i18n_feedback 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +18 -2
- data/Rakefile +3 -0
- data/app/controllers/i18n_feedback/suggestions_controller.rb +15 -1
- data/app/models/i18n_feedback/suggestion.rb +15 -2
- data/lib/generators/i18n_feedback/install/templates/create_i18n_feedback_suggestions.rb.tt +2 -0
- data/lib/i18n_feedback/configuration.rb +6 -0
- data/lib/i18n_feedback/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fdbfd25f140436e323ceb8eec7f22aaa1aa52d4e9979cb0e6cddf4020bf53a9
|
|
4
|
+
data.tar.gz: a6e68e6e68cc021427164c95f671a012c78c31855aa9d1fef5c8cea528577ca1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 822a81c0d8e8d08c2d6e03a9208e899a2a5a6649bbafe347b3e56c3ab6c430bfb059e05ec03abde8307c2a9692f08c64d5f9b7a0c94fe8c10eefead0385ca845
|
|
7
|
+
data.tar.gz: cd0d806e70019c47b00572bf16e64a13b6435337f36affc6a9f283df8949f56d575fcfe0d35b147d558112990c1e07a0ef9dda3b98808bede245fbd3feed2b8e
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.5.0]
|
|
6
|
+
|
|
7
|
+
- Add a `status` to each suggestion — a string-backed Active Record enum with
|
|
8
|
+
values `pending`, `applied`, and `rejected` (`I18nFeedback::Suggestion::STATUSES`),
|
|
9
|
+
`status_`-prefixed (`status_applied?`, `status_applied!`, `Suggestion.status_pending`)
|
|
10
|
+
plus a `newest_first` scope. New suggestions start `pending`; the popover's
|
|
11
|
+
"already suggested" context now shows only pending ones, so applied/rejected
|
|
12
|
+
wordings stop resurfacing.
|
|
13
|
+
|
|
14
|
+
**Upgrading an existing install:** add the column with a migration —
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
add_column :i18n_feedback_suggestions, :status, :string, null: false, default: "pending"
|
|
18
|
+
add_index :i18n_feedback_suggestions, :status
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- Harden the public submission endpoint against abuse:
|
|
22
|
+
- Per-IP rate limiting via Rails' built-in limiter (Rails 7.2+; a no-op on
|
|
23
|
+
7.1). Default 30 requests / 60s, tunable or disable-able via
|
|
24
|
+
`config.rate_limit`; returns `429 Too Many Requests`.
|
|
25
|
+
- Length caps on the stored free-text fields (`proposed_value` / `old_value`
|
|
26
|
+
5000, `comment` / `page_url` 2000, `translation_key` 500) so a client can't
|
|
27
|
+
bloat the table with unbounded input.
|
|
28
|
+
|
|
29
|
+
- Add a `.github/workflows/release.yml` that publishes to RubyGems via trusted
|
|
30
|
+
publishing (OIDC) when a `v*` tag is pushed — no stored credentials or MFA
|
|
31
|
+
prompt.
|
|
32
|
+
|
|
5
33
|
## [0.4.0]
|
|
6
34
|
|
|
7
35
|
- Add a `config.on_submit` hook, called with each saved suggestion right after
|
data/README.md
CHANGED
|
@@ -116,6 +116,10 @@ I18nFeedback.configure do |config|
|
|
|
116
116
|
|
|
117
117
|
# Keep in sync with the `mount` in config/routes.rb.
|
|
118
118
|
config.mount_path = "/i18n_feedback"
|
|
119
|
+
|
|
120
|
+
# Per-IP throttle on the submission endpoint, passed to Rails' built-in rate
|
|
121
|
+
# limiter (Rails 7.2+; ignored on 7.1). Set nil to disable.
|
|
122
|
+
config.rate_limit = { to: 30, within: 60 }
|
|
119
123
|
end
|
|
120
124
|
```
|
|
121
125
|
|
|
@@ -226,13 +230,25 @@ blue accent stays the same in both.
|
|
|
226
230
|
Suggestions are ordinary records:
|
|
227
231
|
|
|
228
232
|
```ruby
|
|
229
|
-
I18nFeedback::Suggestion.
|
|
233
|
+
I18nFeedback::Suggestion.where(status: "pending").newest_first.each do |s|
|
|
230
234
|
puts "#{s.locale} #{s.translation_key}: #{s.old_value.inspect} -> #{s.proposed_value.inspect}"
|
|
231
235
|
end
|
|
232
236
|
```
|
|
233
237
|
|
|
234
238
|
Each row stores `translation_key`, `locale`, `old_value`, `proposed_value`,
|
|
235
|
-
`comment`, `page_url`, and optional `author_id` / `author_label`.
|
|
239
|
+
`comment`, `page_url`, `status`, and optional `author_id` / `author_label`.
|
|
240
|
+
|
|
241
|
+
Every suggestion has a `status` — one of `pending`, `applied`, or `rejected`
|
|
242
|
+
(`I18nFeedback::Suggestion::STATUSES`), backed by an Active Record enum. New
|
|
243
|
+
suggestions start `pending`; once you apply a wording to your locale files or
|
|
244
|
+
decide against it, set the status accordingly so the popover stops offering it
|
|
245
|
+
as pending context:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
suggestion.status_applied! # bang setter
|
|
249
|
+
suggestion.status_applied? # => true
|
|
250
|
+
I18nFeedback::Suggestion.status_pending.newest_first # scope per status
|
|
251
|
+
```
|
|
236
252
|
|
|
237
253
|
### Getting notified
|
|
238
254
|
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'bundler/setup'
|
|
4
|
+
# Defines build/install/release — `rake release` is what the trusted-publishing
|
|
5
|
+
# workflow (.github/workflows/release.yml) runs to push the gem to RubyGems.
|
|
6
|
+
require 'bundler/gem_tasks'
|
|
4
7
|
|
|
5
8
|
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
|
6
9
|
load 'rails/tasks/engine.rake' if File.exist?(APP_RAKEFILE)
|
|
@@ -2,12 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module I18nFeedback
|
|
4
4
|
class SuggestionsController < ApplicationController
|
|
5
|
+
# Throttle the public submission endpoint per IP so one user or bot can't
|
|
6
|
+
# flood the table. Uses the rate limiter built into Rails 7.2+ (backed by
|
|
7
|
+
# Rails.cache); a no-op on Rails 7.1. Tune or disable via config.rate_limit —
|
|
8
|
+
# read once at boot, after the host's initializer.
|
|
9
|
+
if respond_to?(:rate_limit) && I18nFeedback.config.rate_limit
|
|
10
|
+
rate_limit(**I18nFeedback.config.rate_limit,
|
|
11
|
+
only: :create,
|
|
12
|
+
with: lambda {
|
|
13
|
+
render json: { errors: ['Too many suggestions. Please slow down and try again.'] },
|
|
14
|
+
status: :too_many_requests
|
|
15
|
+
})
|
|
16
|
+
end
|
|
17
|
+
|
|
5
18
|
# Pending suggestions for one key/locale, shown as read-only context when the
|
|
6
19
|
# proofreader reopens the popover for a string someone already flagged.
|
|
7
20
|
def index
|
|
8
21
|
suggestions = Suggestion
|
|
9
22
|
.where(translation_key: params[:key], locale: params[:locale])
|
|
10
|
-
.
|
|
23
|
+
.status_pending
|
|
24
|
+
.newest_first
|
|
11
25
|
.limit(20)
|
|
12
26
|
|
|
13
27
|
render json: suggestions.map { |suggestion| suggestion_json(suggestion) }
|
|
@@ -5,10 +5,23 @@ module I18nFeedback
|
|
|
5
5
|
# is optional and stored as loose fields (no foreign key to the host's user
|
|
6
6
|
# table) so the model is portable across apps with different user models.
|
|
7
7
|
class Suggestion < ApplicationRecord
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
# Lifecycle of a proposed wording: a fresh suggestion is `pending` until a
|
|
9
|
+
# developer applies it to the locale files or rejects it. String-backed so
|
|
10
|
+
# the column stays human-readable; prefixed so the generated methods read as
|
|
11
|
+
# `status_pending?` / `Suggestion.status_applied` and never clash.
|
|
12
|
+
STATUSES = %w[pending applied rejected].freeze
|
|
13
|
+
|
|
14
|
+
enum :status, STATUSES.index_by(&:itself), prefix: :status, default: :pending
|
|
15
|
+
|
|
16
|
+
validates :translation_key, presence: true, length: { maximum: 500 }
|
|
17
|
+
validates :proposed_value, presence: true, length: { maximum: 5_000 }
|
|
18
|
+
validates :old_value, length: { maximum: 5_000 }
|
|
19
|
+
validates :comment, length: { maximum: 2_000 }
|
|
20
|
+
validates :page_url, length: { maximum: 2_000 }
|
|
10
21
|
validates :locale,
|
|
11
22
|
presence: true,
|
|
12
23
|
inclusion: { in: ->(_) { I18nFeedback.config.available_locales.call.map(&:to_s) } }
|
|
24
|
+
|
|
25
|
+
scope :newest_first, -> { order(id: :desc) }
|
|
13
26
|
end
|
|
14
27
|
end
|
|
@@ -9,6 +9,7 @@ class CreateI18nFeedbackSuggestions < ActiveRecord::Migration<%= migration_versi
|
|
|
9
9
|
t.text :proposed_value, null: false
|
|
10
10
|
t.text :comment
|
|
11
11
|
t.string :page_url
|
|
12
|
+
t.string :status, null: false, default: 'pending'
|
|
12
13
|
t.string :author_id
|
|
13
14
|
t.string :author_label
|
|
14
15
|
|
|
@@ -16,5 +17,6 @@ class CreateI18nFeedbackSuggestions < ActiveRecord::Migration<%= migration_versi
|
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
add_index :i18n_feedback_suggestions, %i[translation_key locale]
|
|
20
|
+
add_index :i18n_feedback_suggestions, :status
|
|
19
21
|
end
|
|
20
22
|
end
|
|
@@ -52,6 +52,11 @@ module I18nFeedback
|
|
|
52
52
|
# ticket. Runs inline after save; keep it fast or hand off to a job.
|
|
53
53
|
attr_accessor :on_submit
|
|
54
54
|
|
|
55
|
+
# Per-IP throttle for the public submission endpoint, as keyword arguments
|
|
56
|
+
# for Rails' rate limiter (Rails 7.2+; ignored on 7.1). Read once when the
|
|
57
|
+
# controller loads — set it in an initializer. nil disables throttling.
|
|
58
|
+
attr_accessor :rate_limit
|
|
59
|
+
|
|
55
60
|
def initialize
|
|
56
61
|
@enabled_environments = %w[development staging]
|
|
57
62
|
@enabled = ->(_request) { true }
|
|
@@ -64,6 +69,7 @@ module I18nFeedback
|
|
|
64
69
|
@pill_label = nil
|
|
65
70
|
@toggle_param = 'i18n_feedback'
|
|
66
71
|
@on_submit = ->(_suggestion) {}
|
|
72
|
+
@rate_limit = { to: 30, within: 60 }
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
def environment_enabled?
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: i18n_feedback
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -97,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
97
97
|
- !ruby/object:Gem::Version
|
|
98
98
|
version: '0'
|
|
99
99
|
requirements: []
|
|
100
|
-
rubygems_version:
|
|
100
|
+
rubygems_version: 3.6.9
|
|
101
101
|
specification_version: 4
|
|
102
102
|
summary: 'In-context i18n proofreading for Rails: click any translated string and
|
|
103
103
|
suggest a fix.'
|