i18n_proofreading 0.10.4 → 0.10.6

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: 3546b2ddcbb15b380075959eee5ea843e5c0e5cf52748101b0a1e57774365699
4
- data.tar.gz: cb3bb010786b1d7f307c8cd850f372a8f9b5424765c26840d472f68ffe815eb8
3
+ metadata.gz: ca180baf5c569ccbce1681b0a26d914f7846225fef225ce0f3f5bb3a52b5e1f6
4
+ data.tar.gz: bbd44a938dcc053bc63d466cdca8b9ef65ec4360b8cabac2ac7e92b25304991f
5
5
  SHA512:
6
- metadata.gz: 5e4b71924974509622d7c9d8d139e922fdd116ffded96f64ec6da94797cf09c96797328e6a76d12b169a99128a6de39f7daa48d67662bb513ee096ca4d865931
7
- data.tar.gz: c729fef9100594e0fa53a7fcd81f5678eff7c2d8bc4b3cf56de1196e32f45997081e5e3f3080e065af3f8545388f4fe62d5f0cefbd43011f1103244bed678efe
6
+ metadata.gz: 7ff93f334e19313d03ee861c7da265fb8bb98047ca02e189a5d48e0660cc6012578da93508aa91b75dba64a8e4e70405a88a50ac935b93955771358fb3794399
7
+ data.tar.gz: 68f52f53cc1f2067a5f75920d282412de882463c394470276ac7495ba439d7a1a04f1fe0270b130b5bfbb706c6fc5893024e4829247bbd3624cf3de58641f714
data/AGENTS.md ADDED
@@ -0,0 +1,149 @@
1
+ # AGENTS.md
2
+
3
+ Instructions for coding agents. Two audiences:
4
+
5
+ - **[Installing i18n_proofreading into a Rails app](#installing-into-a-rails-app)** — you are working in a host app and were asked to add in-context translation review or a way for someone to suggest better wording.
6
+ - **[Working on the gem itself](#working-on-the-gem-itself)** — you are working in this repository.
7
+
8
+ Requirements: Ruby >= 3.2, Rails >= 7.1. The widget needs the CSRF token from `csrf_meta_tags`, which a standard Rails layout already has.
9
+
10
+ **Read this first: the gem never writes to your locale files, and it is not a production tool.** Both are deliberate, and both are covered below.
11
+
12
+ If you are in a host app and this file is not in front of you, it ships inside the gem: `cat "$(bundle show i18n_proofreading)/AGENTS.md"`.
13
+
14
+ ---
15
+
16
+ ## Installing into a Rails app
17
+
18
+ ### 1. Install
19
+
20
+ ```bash
21
+ bundle add i18n_proofreading
22
+ bin/rails generate i18n_proofreading:install
23
+ bin/rails db:migrate
24
+ ```
25
+
26
+ The generator writes `config/initializers/i18n_proofreading.rb`, one migration (`i18n_proofreading_suggestions`), and mounts the engine. Read the initializer it wrote — every option is documented there in comments, and it is the source of truth over any summary of it, including this file.
27
+
28
+ ### 2. There is no step 2 — do not edit the layout
29
+
30
+ The widget injects itself into HTML responses through a Rack middleware, so **no layout change is needed**. Boot the app in development and look for the **"Suggest edits"** pill bottom-left. Click it, then click any text; `Esc` exits.
31
+
32
+ Only if the host prefers to place it explicitly:
33
+
34
+ ```ruby
35
+ config.auto_inject = false
36
+ ```
37
+
38
+ ```erb
39
+ <%= i18n_proofreading_tag %>
40
+ ```
41
+
42
+ Do not do both. And do not go looking for a missing `<%= … %>` when the pill does not appear — auto-injection is the default, so the cause is almost always the environment gate below.
43
+
44
+ ### 3. Before deploying: understand the two gates
45
+
46
+ **a. Environments.** The tool is active only in `config.enabled_environments`, which defaults to `%w[development staging]`. In every other environment it does nothing at all: no key markers, no endpoint, and the I18n backend patch that marks keys is never even prepended. **Do not add `production` to that list to "let the client review the live site."** Marking every translated string in production is a user-visible change to every page, and the review endpoint is not built to be public.
47
+
48
+ **b. The dashboard.** `/i18n_proofreading` defaults to **development only** and is independent of the widget gates, so a maintainer can triage from production while the widget stays off. It fails closed:
49
+
50
+ ```ruby
51
+ config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
52
+ ```
53
+
54
+ > **`enabled`, `authorize_admin` and `current_user` receive the raw `request`, not a controller.** Writing `->(request) { current_user }` is the most common mistake here — that method does not exist in this scope. Resolve the user *from the request*: Warden env, a signed cookie, `Current.user` if middleware already set it. `author_label` is the exception: it receives whatever `current_user` returned.
55
+
56
+ ### 4. Verify
57
+
58
+ ```bash
59
+ bin/rails routes | grep i18n_proofreading # engine mounted
60
+ bin/rails i18n_proofreading:seed_demo # optional sample suggestions, idempotent
61
+ ```
62
+
63
+ Then in development: load a page, confirm the pill appears bottom-left, suggest a change to any string, and read it back at `/i18n_proofreading`.
64
+
65
+ ### The gem does not apply suggestions, and that is the point
66
+
67
+ A suggestion is a row: the i18n key, the old value, the proposal, an optional comment, a locale, and a status (`pending` / `applied` / `rejected`). Nothing in the gem edits `config/locales/*.yml`, and **the dashboard is read-only — `index` and `show`, with no update or destroy action.** Applying a wording change means a human (or your own tooling) editing the YAML and committing it.
68
+
69
+ So: **do not ask this gem to rewrite locale files, and do not build a "click to apply" button expecting an endpoint to exist.** If the app wants status bookkeeping, do it deliberately from your own code or the console — the enum is prefixed:
70
+
71
+ ```ruby
72
+ I18nProofreading::Suggestion.status_pending.find(id).status_applied!
73
+ ```
74
+
75
+ If you are asked to automate applying suggestions, that is host-app work: read the rows, write the YAML, review the diff in a pull request. Treat the suggestion table as an inbox, not as the source of truth for translations.
76
+
77
+ ### Turning suggest mode on from your own UI
78
+
79
+ The pill is one way; a link is another, and the choice is remembered in a cookie so the rest of the app stays in suggest mode:
80
+
81
+ ```
82
+ ?i18n_proofreading=true # on
83
+ ?i18n_proofreading=false # off
84
+ ```
85
+
86
+ `config.show_pill = false` hides the pill, `config.pill_label` overrides its text (nil = the localized `i18n_proofreading.pill` key), and `config.toggle_param` renames the parameter.
87
+
88
+ ### Do not
89
+
90
+ - **Do not add `production` to `enabled_environments`** (see above).
91
+ - **Do not copy the widget JavaScript into `app/javascript`, or add a `<script>` tag for it.** The middleware injects what is needed and the engine serves the code same-origin — which is what lets it run under a nonce-based CSP, including `strict-dynamic`, across Turbo body swaps.
92
+ - **Do not edit the layout** for the default install — auto-injection is the default.
93
+ - **Do not expect the gem to write YAML** (see above).
94
+ - **Do not set config outside the initializer.** `rate_limit` in particular is read once when the controller class loads; assigning config per-request mutates it process-wide.
95
+
96
+ ### Configuration worth knowing
97
+
98
+ Everything is optional; a fresh install works with zero config in development. Full list with comments is in the generated initializer.
99
+
100
+ | Option | Default | Note |
101
+ | --- | --- | --- |
102
+ | `enabled_environments` | `%w[development staging]` | The hard gate. Never add production |
103
+ | `enabled` | everyone | Per-request gate on top of the environment check |
104
+ | `authorize_admin` | development only | **Who can read the dashboard.** Independent of the above |
105
+ | `admin_layout` | `i18n_proofreading/application` | Render the dashboard in your admin shell |
106
+ | `current_user` | `nil` | Receives the request |
107
+ | `author_label` | email, else `to_s` | Receives the user |
108
+ | `available_locales` | `I18n.available_locales` | Callable; validates what a suggestion may target |
109
+ | `auto_inject` | `true` | `false` = place `i18n_proofreading_tag` yourself |
110
+ | `show_pill`, `pill_label` | `true`, localized | Hide the pill and use `?i18n_proofreading=true` instead |
111
+ | `toggle_param` | `"i18n_proofreading"` | Rename the query parameter |
112
+ | `rate_limit` | `{ to: 30, within: 60 }` | Rails 7.2+; ignored on 7.1. `nil` disables |
113
+ | `mount_path` | `"/i18n_proofreading"` | Keep in sync with the `mount` line |
114
+ | `on_submit` | no-op | Runs inline after save — Slack, email, a ticket |
115
+
116
+ The tool's own UI ships in 26 languages, RTL mirrored, and follows system light/dark.
117
+
118
+ ### Common failure modes
119
+
120
+ | Symptom | Cause |
121
+ | --- | --- |
122
+ | No pill, no outlines | The environment is not in `enabled_environments` (this is the usual one), or `config.enabled` returned false, or `show_pill = false` |
123
+ | Pill appears but no strings are outlined | The I18n backend patch is only prepended in an enabled environment at boot — check you are actually in development/staging, and restart after changing the setting |
124
+ | `/i18n_proofreading` returns a 403 | `authorize_admin` still at its development-only default |
125
+ | Suggestions rejected with an invalid-token error | The layout is missing `csrf_meta_tags` |
126
+ | A suggestion is rejected as an invalid locale | It must be in `config.available_locales` |
127
+ | Nothing changes in the app after a suggestion is accepted | Expected. The gem never writes locale files — a human edits the YAML |
128
+ | `undefined local variable current_user` in the initializer | A gate lambda treated its argument as a controller. It is a `request` |
129
+
130
+ ---
131
+
132
+ ## Working on the gem itself
133
+
134
+ ```bash
135
+ bundle exec rake test # minitest, dummy app under test/dummy
136
+ bundle exec rubocop # must be clean
137
+ BUNDLE_GEMFILE=gemfiles/rails_7.1.gemfile bundle exec rake test # 7.1, 7.2, 8.0, 8.1 in gemfiles/
138
+ ```
139
+
140
+ Layout: `app/` controller, `Suggestion`, dashboard views · `lib/i18n_proofreading/` config, middleware, the I18n marking backend, widget JS, seeds, engine · `lib/generators/i18n_proofreading/install/` the one generator · `config/locales/` 26 locales · `test/` minitest with `test/dummy` as the host app.
141
+
142
+ Conventions this codebase holds to — follow them rather than the first thing that works:
143
+
144
+ - **Production carries none of it.** The marking backend is prepended in `after_initialize` only when `environment_enabled?`, so a production boot never even patches I18n. Anything new must keep that property: no markers, no endpoint, no patch outside the enabled environments.
145
+ - **The tool never writes to the host's locale files.** That is why there is deliberately no update or destroy route, and why the dashboard is read-only. Do not add an "apply" action that edits YAML.
146
+ - **The widget is injected by middleware and served same-origin**, which is what keeps it working under a nonce-based CSP with `strict-dynamic` across Turbo body swaps. Do not inline it.
147
+ - **Key marking must degrade to plain strings.** A host that reads translations outside a request, or in an environment where the tool is off, has to get ordinary values back.
148
+ - Every user-facing change bumps `lib/i18n_proofreading/version.rb` and adds a `CHANGELOG.md` entry (Keep a Changelog format) that says what it costs, not only what it adds.
149
+ - Commit messages are prose that explains the tradeoff — read `git log` before writing one.
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.10.6]
6
+
7
+ - Added `AGENTS.md`: install and integration instructions written for coding
8
+ agents — that no layout change is needed because the widget is injected by
9
+ middleware, that the environment gate (never production) is the usual reason
10
+ the pill is missing, the request-shaped config lambdas, and above all that the
11
+ gem never writes to locale files, so there is no "apply" endpoint to build
12
+ against. It ships inside the gem, so
13
+ `cat "$(bundle show i18n_proofreading)/AGENTS.md"` works from a host app.
14
+ - Corrected the `authorize_admin` comment in the configuration: it described the
15
+ dashboard as changing status and deleting, but the dashboard has been
16
+ read-only (`index` and `show` only) for as long as that route file has said so.
17
+
18
+ ## [0.10.5]
19
+
20
+ - Added `config.admin_layout`, letting host apps render the proofreading
21
+ dashboard inside their own admin layout while keeping the standalone gem
22
+ layout as the default.
23
+
5
24
  ## [0.10.4]
6
25
 
7
26
  - Redesigned the proofreading admin dashboard into a two-column review layout
data/README.md CHANGED
@@ -26,6 +26,16 @@ bin/rails generate i18n_proofreading:install
26
26
  bin/rails db:migrate
27
27
  ```
28
28
 
29
+ Optional demo data:
30
+
31
+ ```bash
32
+ bin/rails i18n_proofreading:seed_demo
33
+ ```
34
+
35
+ It creates three idempotent sample suggestions across pending, applied, and
36
+ rejected states. Running the task again refreshes those records instead of
37
+ duplicating them.
38
+
29
39
  Boot the app in development and look for the **"Suggest edits"** pill in the
30
40
  bottom-left. Click it, then click any text. `Esc` exits.
31
41
 
@@ -39,6 +49,12 @@ No layout change needed — the widget injects itself into HTML responses.
39
49
  Ruby >= 3.2 · Rails >= 7.1 · CSRF token comes from `csrf_meta_tags`, already in
40
50
  a standard Rails layout.
41
51
 
52
+ Installing with a coding agent? Point it at [AGENTS.md](AGENTS.md) — the same
53
+ steps in the order an agent needs them, the gates it tends to get wrong, and the
54
+ things it should not do (starting with: this gem never writes your locale files).
55
+ It ships inside the gem, so `cat "$(bundle show i18n_proofreading)/AGENTS.md"`
56
+ works from any app that bundles it.
57
+
42
58
  ## What you get
43
59
 
44
60
  | | |
@@ -6,6 +6,10 @@ module I18nProofreading
6
6
 
7
7
  private
8
8
 
9
+ def i18n_proofreading_admin_layout
10
+ I18nProofreading.config.admin_layout
11
+ end
12
+
9
13
  # Gate for the widget's public API (submit a suggestion, read prior context).
10
14
  # The client can set the cookie, but it can never reach the endpoints unless
11
15
  # the app itself says the tool is available for this request.
@@ -9,7 +9,7 @@ module I18nProofreading
9
9
  before_action :require_admin, only: %i[index show]
10
10
  before_action :set_suggestion, only: :show
11
11
 
12
- layout 'i18n_proofreading/application', only: %i[index show]
12
+ layout :i18n_proofreading_admin_layout, only: %i[index show]
13
13
 
14
14
  # Throttle the public submission endpoint per IP so one user or bot can't
15
15
  # flood the table. Uses the rate limiter built into Rails 7.2+ (backed by
@@ -27,6 +27,7 @@ module I18nProofreading
27
27
 
28
28
  def post_install
29
29
  say "\ni18n_proofreading installed. Run `rails db:migrate`, then boot in development", :green
30
+ say 'Optional: run `bin/rails i18n_proofreading:seed_demo` for sample suggestions.'
30
31
  say "and look for the “Suggest edits” pill in the bottom-left corner.\n"
31
32
  end
32
33
 
@@ -16,6 +16,11 @@ I18nProofreading.configure do |config|
16
16
  # development only; wire it to your own admin check to open it elsewhere.
17
17
  #
18
18
  # config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
19
+ #
20
+ # Render the dashboard inside your app's admin layout. Default: the gem's
21
+ # standalone dashboard layout.
22
+ #
23
+ # config.admin_layout = "admin/application"
19
24
 
20
25
  # Attribute a suggestion to a user (optional). Return an object responding to
21
26
  # #id (ideally #email too), or nil. Receives the Rack::Request. You resolve the
@@ -14,13 +14,19 @@ module I18nProofreading
14
14
  # feature flag, an allowlist, etc.
15
15
  attr_accessor :enabled
16
16
 
17
- # Per-request gate for the triage dashboard (browse suggestions, change their
18
- # status, delete). Independent of `enabled` and `enabled_environments`: the
17
+ # Per-request gate for the triage dashboard, which is read-only — browse and
18
+ # read suggestions; there is deliberately no update or destroy, since the
19
+ # tool never writes to the host's locale files.
20
+ # Independent of `enabled` and `enabled_environments`: the
19
21
  # widget is dev/staging-only, but a maintainer may want to triage from
20
22
  # production. Defaults to development only, so a fresh install never exposes
21
23
  # the dashboard until you wire it to your own admin check.
22
24
  attr_accessor :authorize_admin
23
25
 
26
+ # Layout used by the triage dashboard. Override this to render it inside
27
+ # your app's admin shell, e.g. "admin/application".
28
+ attr_accessor :admin_layout
29
+
24
30
  # Resolve the current user for attribution (optional). Return an object
25
31
  # responding to #id, or nil. Receives the Rack::Request.
26
32
  attr_accessor :current_user
@@ -68,6 +74,7 @@ module I18nProofreading
68
74
  @enabled_environments = %w[development staging]
69
75
  @enabled = ->(_request) { true }
70
76
  @authorize_admin = ->(_request) { Rails.env.development? }
77
+ @admin_layout = 'i18n_proofreading/application'
71
78
  @current_user = ->(_request) {}
72
79
  @author_label = ->(user) { user.respond_to?(:email) ? user.email : user&.to_s }
73
80
  @available_locales = -> { I18n.available_locales.map(&:to_s) }
@@ -4,6 +4,10 @@ module I18nProofreading
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace I18nProofreading
6
6
 
7
+ rake_tasks do
8
+ load File.expand_path('../tasks/i18n_proofreading_tasks.rake', __dir__)
9
+ end
10
+
7
11
  initializer 'i18n_proofreading.middleware' do |app|
8
12
  app.middleware.use I18nProofreading::Middleware
9
13
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nProofreading
4
+ module Seeds
5
+ SUGGESTIONS = [
6
+ {
7
+ translation_key: 'dashboard.title',
8
+ locale: 'en',
9
+ old_value: 'Dashboard',
10
+ proposed_value: 'Overview',
11
+ comment: 'Shorter and clearer for the first screen.',
12
+ page_url: '/dashboard',
13
+ status: 'pending',
14
+ author_id: 'i18n-proofreading-demo:reviewer',
15
+ author_label: 'Demo Reviewer'
16
+ },
17
+ {
18
+ translation_key: 'billing.cta',
19
+ locale: 'en',
20
+ old_value: 'Go',
21
+ proposed_value: 'Update billing details',
22
+ comment: 'The button should say what will happen.',
23
+ page_url: '/billing',
24
+ status: 'applied',
25
+ author_id: 'i18n-proofreading-demo:copywriter',
26
+ author_label: 'Demo Copywriter'
27
+ },
28
+ {
29
+ translation_key: 'settings.cancel',
30
+ locale: 'fr',
31
+ old_value: 'Annuler',
32
+ proposed_value: 'Supprimer le compte',
33
+ comment: 'Rejected example: this changes the meaning.',
34
+ page_url: '/settings',
35
+ status: 'rejected',
36
+ author_id: 'i18n-proofreading-demo:reviewer',
37
+ author_label: 'Demo Reviewer'
38
+ }
39
+ ].freeze
40
+
41
+ def self.load!
42
+ SUGGESTIONS.map do |attributes|
43
+ localized = attributes.merge(locale: locale_for(attributes.fetch(:locale)))
44
+ suggestion = I18nProofreading::Suggestion.find_or_initialize_by(
45
+ translation_key: localized.fetch(:translation_key),
46
+ locale: localized.fetch(:locale),
47
+ author_id: localized.fetch(:author_id)
48
+ )
49
+ suggestion.assign_attributes(localized)
50
+ suggestion.save!
51
+ suggestion
52
+ end
53
+ end
54
+
55
+ def self.locale_for(preferred)
56
+ locales = I18nProofreading.config.available_locales.call.map(&:to_s)
57
+ return preferred if locales.include?(preferred)
58
+
59
+ locales.first || I18n.default_locale.to_s
60
+ end
61
+ private_class_method :locale_for
62
+ end
63
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18nProofreading
4
- VERSION = '0.10.4'
4
+ VERSION = '0.10.6'
5
5
  end
@@ -5,6 +5,7 @@ require 'i18n_proofreading/configuration'
5
5
  require 'i18n_proofreading/marking'
6
6
  require 'i18n_proofreading/widget'
7
7
  require 'i18n_proofreading/middleware'
8
+ require 'i18n_proofreading/seeds'
8
9
  require 'i18n_proofreading/engine'
9
10
 
10
11
  # In-context translation proofreading for Rails. Renders each i18n key alongside
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :i18n_proofreading do
4
+ desc 'Create or refresh i18n_proofreading demo suggestions'
5
+ task seed_demo: :environment do
6
+ suggestions = I18nProofreading::Seeds.load!
7
+ puts "Seeded #{suggestions.size} i18n proofreading demo suggestions."
8
+ end
9
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n_proofreading
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.4
4
+ version: 0.10.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Shmarov
@@ -34,6 +34,7 @@ executables: []
34
34
  extensions: []
35
35
  extra_rdoc_files: []
36
36
  files:
37
+ - AGENTS.md
37
38
  - CHANGELOG.md
38
39
  - MIT-LICENSE
39
40
  - README.md
@@ -84,9 +85,11 @@ files:
84
85
  - lib/i18n_proofreading/engine.rb
85
86
  - lib/i18n_proofreading/marking.rb
86
87
  - lib/i18n_proofreading/middleware.rb
88
+ - lib/i18n_proofreading/seeds.rb
87
89
  - lib/i18n_proofreading/version.rb
88
90
  - lib/i18n_proofreading/widget.js
89
91
  - lib/i18n_proofreading/widget.rb
92
+ - lib/tasks/i18n_proofreading_tasks.rake
90
93
  homepage: https://github.com/yshmarov/i18n_proofreading
91
94
  licenses:
92
95
  - MIT