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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4dee71413e641b59a88d1116ac134410395055d31f097d83800155feead9fc8
4
- data.tar.gz: 4e41cd76dd83acbdd77ff95ece48bed916040bdf0e6227e78aa6090f8c28025b
3
+ metadata.gz: 9fdbfd25f140436e323ceb8eec7f22aaa1aa52d4e9979cb0e6cddf4020bf53a9
4
+ data.tar.gz: a6e68e6e68cc021427164c95f671a012c78c31855aa9d1fef5c8cea528577ca1
5
5
  SHA512:
6
- metadata.gz: 9483d2a561d1edb370685c85ac3dfaa5030532c58102aeb923926ea180de687d738e9e7deccbf8a6689f617f4ae7da7a20750272a5306419d60389d574c5b4f4
7
- data.tar.gz: d50924f6b93eec7fe93bb5e5baa8a7a53a43e8fdcf864b571531646d3f47f8554dff87e6766289ce0589830f0d354176759ea85ebadbff6722396664aec632b9
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.order(created_at: :desc).each do |s|
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
- .order(id: :desc)
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
- validates :translation_key, presence: true
9
- validates :proposed_value, presence: true
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?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18nFeedback
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
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.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: 4.0.6
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.'