i18n_proofreading 0.10.5 → 0.11.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/AGENTS.md +149 -0
- data/CHANGELOG.md +40 -0
- data/README.md +17 -0
- data/app/controllers/concerns/i18n_proofreading/request_context.rb +41 -0
- data/app/controllers/i18n_proofreading/application_controller.rb +9 -29
- data/app/controllers/i18n_proofreading/dashboard_controller.rb +30 -0
- data/app/controllers/i18n_proofreading/submissions_controller.rb +74 -0
- data/app/controllers/i18n_proofreading/suggestions_controller.rb +1 -64
- data/app/views/i18n_proofreading/shared/_dashboard.html.erb +13 -0
- data/app/views/i18n_proofreading/suggestions/index.html.erb +88 -86
- data/app/views/i18n_proofreading/suggestions/show.html.erb +7 -5
- data/app/views/layouts/i18n_proofreading/application.html.erb +8 -5
- data/config/routes.rb +7 -3
- data/lib/generators/i18n_proofreading/install/install_generator.rb +3 -6
- data/lib/generators/i18n_proofreading/install/templates/create_i18n_proofreading_suggestions.rb.tt +1 -1
- data/lib/generators/i18n_proofreading/install/templates/initializer.rb +8 -0
- data/lib/generators/i18n_proofreading/migration_helpers.rb +28 -0
- data/lib/i18n_proofreading/configuration.rb +25 -3
- data/lib/i18n_proofreading/dashboard.css +153 -156
- data/lib/i18n_proofreading/engine.rb +4 -0
- data/lib/i18n_proofreading/seeds.rb +63 -0
- data/lib/i18n_proofreading/version.rb +1 -1
- data/lib/i18n_proofreading.rb +9 -0
- data/lib/tasks/i18n_proofreading_tasks.rake +9 -0
- metadata +9 -1
|
@@ -1,161 +1,158 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
17
|
-
}
|
|
18
|
-
a { color: var(--accent); text-decoration: none; }
|
|
19
|
-
a:hover { text-decoration: underline; }
|
|
20
|
-
.container { max-width: 960px; margin: 0 auto; padding: 24px 16px 64px; }
|
|
21
|
-
h1 { font-size: 22px; margin: 0; }
|
|
22
|
-
.lede { color: var(--muted); margin: 4px 0 0; }
|
|
23
|
-
header.page { margin-bottom: 20px; }
|
|
24
|
-
|
|
25
|
-
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 16px; flex-wrap: wrap; }
|
|
26
|
-
.tabs a { padding: 8px 14px; border-radius: 8px 8px 0 0; color: var(--muted); }
|
|
27
|
-
.tabs a.active {
|
|
28
|
-
color: var(--text); font-weight: 600; border: 1px solid var(--border);
|
|
29
|
-
border-bottom: 2px solid var(--surface); background: var(--surface); margin-bottom: -1px;
|
|
30
|
-
}
|
|
31
|
-
.tabs a:hover { text-decoration: none; color: var(--text); }
|
|
32
|
-
.count {
|
|
33
|
-
display: inline-block; min-width: 20px; padding: 0 6px; margin-left: 4px; border-radius: 999px;
|
|
34
|
-
background: var(--border); font-size: 12px; text-align: center; font-variant-numeric: tabular-nums;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.filters { margin-bottom: 16px; display: flex; gap: 8px; align-items: center; }
|
|
38
|
-
.filters label { color: var(--muted); font-size: 13px; }
|
|
39
|
-
.filters select {
|
|
40
|
-
padding: 6px 8px; border: 1px solid var(--border); border-radius: 8px;
|
|
41
|
-
background: var(--surface); color: var(--text); font: inherit;
|
|
42
|
-
}
|
|
1
|
+
/* Dashboard styles.
|
|
2
|
+
*
|
|
3
|
+
* Three layers, and the split is what lets a host replace the layout:
|
|
4
|
+
*
|
|
5
|
+
* :root custom properties, all `--ip-` prefixed so they can
|
|
6
|
+
* neither overwrite a host's nor be overwritten by them.
|
|
7
|
+
* .ip-page the page frame (was bare html/body). Only the gem's own
|
|
8
|
+
* layout puts this class on <body>, so inside a host admin
|
|
9
|
+
* these rules simply do not apply.
|
|
10
|
+
* .ip-dashboard everything else, nested. Names like .container, .card
|
|
11
|
+
* and .tabs are confined to the dashboard's own markup, so
|
|
12
|
+
* loading this file inside a host cannot restyle its chrome.
|
|
13
|
+
*
|
|
14
|
+
* Nothing here styles a bare `body`, `a`, `table` or `*` at the top level.
|
|
15
|
+
*/
|
|
43
16
|
|
|
44
|
-
.card {
|
|
45
|
-
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
46
|
-
padding: 14px 16px; margin-bottom: 12px;
|
|
47
|
-
}
|
|
48
|
-
.card.pad { margin: 0; }
|
|
49
|
-
.row-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
|
|
50
|
-
code.key {
|
|
51
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
|
|
52
|
-
background: var(--code); padding: 2px 7px; border-radius: 6px; overflow-wrap: anywhere;
|
|
53
|
-
}
|
|
54
|
-
.badge {
|
|
55
|
-
display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
|
|
56
|
-
color: #fff; white-space: nowrap;
|
|
57
|
-
}
|
|
58
|
-
.badge.status-pending { background: var(--pending); }
|
|
59
|
-
.badge.status-applied { background: var(--applied); }
|
|
60
|
-
.badge.status-rejected { background: var(--rejected); }
|
|
61
|
-
.badge.locale { background: transparent; color: var(--muted); border: 1px solid var(--border); }
|
|
62
|
-
.spacer { flex: 1; }
|
|
63
|
-
.meta { color: var(--muted); font-size: 13px; }
|
|
64
17
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
18
|
+
:root {
|
|
19
|
+
color-scheme: light dark;
|
|
20
|
+
--ip-bg: #f6f7f9; --ip-surface: #fff; --ip-text: #1c2024; --ip-muted: #6b7280;
|
|
21
|
+
--ip-border: #e5e7eb; --ip-accent: #2563eb; --ip-accent-text: #fff; --ip-code: #f0f1f3;
|
|
22
|
+
--ip-pending: #d97706; --ip-applied: #16a34a; --ip-rejected: #6b7280;
|
|
23
|
+
}
|
|
24
|
+
@media (prefers-color-scheme: dark) {
|
|
25
|
+
:root {
|
|
26
|
+
--ip-bg: #111418; --ip-surface: #1a1f26; --ip-text: #e6e8ea; --ip-muted: #9aa2ab;
|
|
27
|
+
--ip-border: #2a313a; --ip-accent: #3b82f6; --ip-code: #232a33;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
@media (max-width: 760px) {
|
|
31
|
+
.ip-page { font-size: 14px; }
|
|
32
|
+
.ip-index, .ip-show { overflow-x: hidden; }
|
|
33
|
+
.ip-index { overflow-y: auto; overflow-x: hidden; }
|
|
34
|
+
.ip-index, .ip-index .container { min-height: 100vh; height: auto; }
|
|
35
|
+
.ip-index .container { padding-bottom: 10px; }
|
|
36
|
+
.ip-dashboard {
|
|
37
|
+
& .container { padding: 14px 10px 24px; }
|
|
38
|
+
& .review-detail,
|
|
39
|
+
& .suggestion-panel,
|
|
40
|
+
& .detail-scroll,
|
|
41
|
+
& .card,
|
|
42
|
+
& dl,
|
|
43
|
+
& dd { min-width: 0; max-width: 100%; }
|
|
44
|
+
& .suggestion-panel .card { width: calc(100vw - 20px); }
|
|
45
|
+
& .lede,
|
|
46
|
+
& dd,
|
|
47
|
+
& .comment { max-width: calc(100vw - 54px); white-space: normal; overflow-wrap: anywhere; }
|
|
48
|
+
& .review-shell { display: block; min-height: calc(100vh - 76px); }
|
|
49
|
+
& .review-sidebar,
|
|
50
|
+
& .review-detail { min-height: calc(100vh - 76px); }
|
|
51
|
+
& .review-shell.has-selected .review-sidebar { display: none; }
|
|
52
|
+
& .review-shell:not(.has-selected) .review-detail { display: none; }
|
|
53
|
+
& .suggestion-list { overflow: visible; }
|
|
54
|
+
& .suggestion-row { padding: 12px; }
|
|
55
|
+
& .suggestion-meta { display: none; }
|
|
56
|
+
& .review-detail .suggestion-panel { min-height: calc(100vh - 64px); }
|
|
57
|
+
& .mobile-back { display: block; flex: 0 0 auto; margin: 10px 10px 0; font-size: 13px; }
|
|
58
|
+
& .panel-head h1 { font-size: 18px; }
|
|
59
|
+
& .panel-head .key { min-width: 0; white-space: normal; overflow-wrap: anywhere; }
|
|
60
|
+
& dl.diff,
|
|
61
|
+
& .meta-list { grid-template-columns: 1fr; gap: 3px; }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
91
64
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
gap: 16px; }
|
|
99
|
-
.review-sidebar { min-width: 0; min-height: 0; display: flex; flex-direction: column;
|
|
100
|
-
background: var(--surface); border: 1px solid var(--border);
|
|
101
|
-
border-radius: 8px; overflow: hidden; }
|
|
102
|
-
.review-sidebar .tabs { flex: 0 0 auto; margin: 12px 12px 0; padding: 0; background: transparent;
|
|
103
|
-
border: 0; border-radius: 0; gap: 6px; flex-wrap: nowrap; }
|
|
104
|
-
.review-sidebar .tabs a { flex: 1 1 0; position: relative; display: flex; align-items: center;
|
|
105
|
-
justify-content: center; gap: 6px; min-height: 34px; padding: 5px 10px 8px;
|
|
106
|
-
border-radius: 8px; color: var(--muted); font-weight: 600; }
|
|
107
|
-
.review-sidebar .tabs a.active { color: var(--text); border: 0; background: transparent; box-shadow: none; margin: 0; }
|
|
108
|
-
.review-sidebar .tabs a.active::after { content: ""; position: absolute; left: 20px; right: 20px; bottom: 0;
|
|
109
|
-
height: 2px; border-radius: 999px; background: var(--accent); }
|
|
110
|
-
.review-sidebar .tabs a:not(.active):hover { text-decoration: none; color: var(--text); background: var(--bg); }
|
|
111
|
-
.review-sidebar .tabs .count { margin-left: 0; background: color-mix(in srgb, var(--muted) 14%, transparent); }
|
|
112
|
-
.review-sidebar .tabs a.active .count { background: var(--border); }
|
|
113
|
-
.review-sidebar .filters { flex: 0 0 auto; margin: 0; padding: 12px; border-bottom: 1px solid var(--border); }
|
|
114
|
-
.suggestion-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
|
115
|
-
.suggestion-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; padding: 13px 14px;
|
|
116
|
-
color: var(--text); border-bottom: 1px solid var(--border); }
|
|
117
|
-
.suggestion-row > .badge { align-self: start; }
|
|
118
|
-
.suggestion-row:hover { text-decoration: none; background: var(--bg); }
|
|
119
|
-
.suggestion-row.active { background: color-mix(in srgb, var(--accent) 9%, var(--surface));
|
|
120
|
-
box-shadow: inset 3px 0 0 var(--accent); }
|
|
121
|
-
.suggestion-main { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
|
|
122
|
-
.suggestion-topline, .suggestion-meta { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
123
|
-
.suggestion-topline .key { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
124
|
-
.suggestion-copy, .suggestion-meta span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
125
|
-
.suggestion-copy { display: block; font-weight: 600; }
|
|
126
|
-
.suggestion-time { margin-left: auto; white-space: nowrap; }
|
|
127
|
-
.review-sidebar .pager { flex: 0 0 auto; margin: 0; padding: 10px 12px; border-top: 1px solid var(--border); }
|
|
128
|
-
.review-detail { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
129
|
-
.review-detail .suggestion-panel { flex: 1 1 auto; min-height: 0; }
|
|
130
|
-
.review-detail .detail-scroll { flex: 1 1 auto; overflow-y: auto; padding-right: 2px; }
|
|
131
|
-
.empty-state { margin: auto; max-width: 320px; padding: 24px; text-align: center; }
|
|
132
|
-
.empty-state h2 { margin: 0 0 6px; font-size: 18px; }
|
|
133
|
-
.empty-state p { margin: 0; }
|
|
134
|
-
.mobile-back { display: none; }
|
|
65
|
+
/* Page frame — the gem's own layout only. */
|
|
66
|
+
.ip-page { margin: 0; background: var(--ip-bg); color: var(--ip-text); font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
67
|
+
.ip-index { overflow: hidden; }
|
|
68
|
+
.ip-index .container { max-width: 1280px; display: flex; flex-direction: column; padding-bottom: 16px; }
|
|
69
|
+
.ip-index header.page { flex: 0 0 auto; margin-bottom: 14px; }
|
|
70
|
+
.ip-show .detail-scroll { overflow: visible; }
|
|
135
71
|
|
|
136
|
-
|
|
72
|
+
/* Dashboard components — any layout. */
|
|
73
|
+
.ip-dashboard {
|
|
74
|
+
& * { box-sizing: border-box; }
|
|
75
|
+
& a { color: var(--ip-accent); text-decoration: none; }
|
|
76
|
+
& a:hover { text-decoration: underline; }
|
|
77
|
+
& .container { max-width: 960px; margin: 0 auto; padding: 24px 16px 64px; }
|
|
78
|
+
& h1 { font-size: 22px; margin: 0; }
|
|
79
|
+
& .lede { color: var(--ip-muted); margin: 4px 0 0; }
|
|
80
|
+
& header.page { margin-bottom: 20px; }
|
|
81
|
+
& .tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--ip-border); margin-bottom: 16px; flex-wrap: wrap; }
|
|
82
|
+
& .tabs a { padding: 8px 14px; border-radius: 8px 8px 0 0; color: var(--ip-muted); }
|
|
83
|
+
& .tabs a.active { color: var(--ip-text); font-weight: 600; border: 1px solid var(--ip-border); border-bottom: 2px solid var(--ip-surface); background: var(--ip-surface); margin-bottom: -1px; }
|
|
84
|
+
& .tabs a:hover { text-decoration: none; color: var(--ip-text); }
|
|
85
|
+
& .count { display: inline-block; min-width: 20px; padding: 0 6px; margin-left: 4px; border-radius: 999px; background: var(--ip-border); font-size: 12px; text-align: center; font-variant-numeric: tabular-nums; }
|
|
86
|
+
& .filters { margin-bottom: 16px; display: flex; gap: 8px; align-items: center; }
|
|
87
|
+
& .filters label { color: var(--ip-muted); font-size: 13px; }
|
|
88
|
+
& .filters select { padding: 6px 8px; border: 1px solid var(--ip-border); border-radius: 8px; background: var(--ip-surface); color: var(--ip-text); font: inherit; }
|
|
89
|
+
& .card { background: var(--ip-surface); border: 1px solid var(--ip-border); border-radius: 12px; padding: 14px 16px; margin-bottom: 12px; }
|
|
90
|
+
& .card.pad { margin: 0; }
|
|
91
|
+
& .row-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
|
|
92
|
+
& code.key { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; background: var(--ip-code); padding: 2px 7px; border-radius: 6px; overflow-wrap: anywhere; }
|
|
93
|
+
& .badge { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; color: #fff; white-space: nowrap; }
|
|
94
|
+
& .badge.status-pending { background: var(--ip-pending); }
|
|
95
|
+
& .badge.status-applied { background: var(--ip-applied); }
|
|
96
|
+
& .badge.status-rejected { background: var(--ip-rejected); }
|
|
97
|
+
& .badge.locale { background: transparent; color: var(--ip-muted); border: 1px solid var(--ip-border); }
|
|
98
|
+
& .spacer { flex: 1; }
|
|
99
|
+
& .meta { color: var(--ip-muted); font-size: 13px; }
|
|
100
|
+
& dl.diff { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; }
|
|
101
|
+
& dl.diff dt { color: var(--ip-muted); font-size: 13px; }
|
|
102
|
+
& dl.diff dd { margin: 0; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
103
|
+
& dd .old { color: var(--ip-muted); }
|
|
104
|
+
& dd .new { font-weight: 600; }
|
|
105
|
+
& .comment { margin-top: 10px; padding: 8px 10px; background: var(--ip-code); border-radius: 8px; font-size: 14px; overflow-wrap: anywhere; }
|
|
106
|
+
& button { padding: 7px 13px; border: 1px solid var(--ip-border); border-radius: 8px; cursor: pointer; background: var(--ip-surface); color: var(--ip-text); font: inherit; font-size: 14px; font-weight: 600; }
|
|
107
|
+
& button:hover { border-color: var(--ip-muted); }
|
|
108
|
+
& :focus-visible { outline: 2px solid var(--ip-accent); outline-offset: 2px; }
|
|
109
|
+
& .empty { padding: 48px 16px; text-align: center; color: var(--ip-muted); border: 1px dashed var(--ip-border); border-radius: 12px; }
|
|
110
|
+
& .pager { margin-top: 16px; display: flex; justify-content: space-between; }
|
|
111
|
+
& .pager .muted { color: var(--ip-muted); }
|
|
112
|
+
& .breadcrumb { margin: 0 0 12px; font-size: 13px; }
|
|
113
|
+
& .case-id { color: var(--ip-muted); font-weight: 400; font-size: 15px; }
|
|
114
|
+
& .panel-head { flex: 0 0 auto; }
|
|
115
|
+
& .panel-head h1 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
116
|
+
& .detail-scroll { min-height: 0; display: flex; flex-direction: column; gap: 12px; }
|
|
117
|
+
& .suggestion-panel { min-width: 0; display: flex; flex-direction: column; }
|
|
118
|
+
& .meta-list { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; }
|
|
119
|
+
& .meta-list dt { color: var(--ip-muted); font-size: 13px; }
|
|
120
|
+
& .meta-list dd { margin: 0; overflow-wrap: anywhere; }
|
|
121
|
+
& /* Desktop review: queue on the left,
|
|
122
|
+
& selected suggestion on the right. */
|
|
123
|
+
.ip-index,
|
|
124
|
+
& .ip-index .container { height: 100vh; height: 100dvh; }
|
|
125
|
+
& .review-shell { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(330px, 430px) 1fr; gap: 16px; }
|
|
126
|
+
& .review-sidebar { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--ip-surface); border: 1px solid var(--ip-border); border-radius: 8px; overflow: hidden; }
|
|
127
|
+
& .review-sidebar .tabs { flex: 0 0 auto; margin: 12px 12px 0; padding: 0; background: transparent; border: 0; border-radius: 0; gap: 6px; flex-wrap: nowrap; }
|
|
128
|
+
& .review-sidebar .tabs a { flex: 1 1 0; position: relative; display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 34px; padding: 5px 10px 8px; border-radius: 8px; color: var(--ip-muted); font-weight: 600; }
|
|
129
|
+
& .review-sidebar .tabs a.active { color: var(--ip-text); border: 0; background: transparent; box-shadow: none; margin: 0; }
|
|
130
|
+
& .review-sidebar .tabs a.active::after { content: ""; position: absolute; left: 20px; right: 20px; bottom: 0; height: 2px; border-radius: 999px; background: var(--ip-accent); }
|
|
131
|
+
& .review-sidebar .tabs a:not(.active):hover { text-decoration: none; color: var(--ip-text); background: var(--ip-bg); }
|
|
132
|
+
& .review-sidebar .tabs .count { margin-left: 0; background: color-mix(in srgb, var(--ip-muted) 14%, transparent); }
|
|
133
|
+
& .review-sidebar .tabs a.active .count { background: var(--ip-border); }
|
|
134
|
+
& .review-sidebar .filters { flex: 0 0 auto; margin: 0; padding: 12px; border-bottom: 1px solid var(--ip-border); }
|
|
135
|
+
& .suggestion-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
|
136
|
+
& .suggestion-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; padding: 13px 14px; color: var(--ip-text); border-bottom: 1px solid var(--ip-border); }
|
|
137
|
+
& .suggestion-row > .badge { align-self: start; }
|
|
138
|
+
& .suggestion-row:hover { text-decoration: none; background: var(--ip-bg); }
|
|
139
|
+
& .suggestion-row.active { background: color-mix(in srgb, var(--ip-accent) 9%, var(--ip-surface)); box-shadow: inset 3px 0 0 var(--ip-accent); }
|
|
140
|
+
& .suggestion-main { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
|
|
141
|
+
& .suggestion-topline,
|
|
142
|
+
& .suggestion-meta { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
143
|
+
& .suggestion-topline .key { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
144
|
+
& .suggestion-copy,
|
|
145
|
+
& .suggestion-meta span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
146
|
+
& .suggestion-copy { display: block; font-weight: 600; }
|
|
147
|
+
& .suggestion-time { margin-left: auto; white-space: nowrap; }
|
|
148
|
+
& .review-sidebar .pager { flex: 0 0 auto; margin: 0; padding: 10px 12px; border-top: 1px solid var(--ip-border); }
|
|
149
|
+
& .review-detail { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
150
|
+
& .review-detail .suggestion-panel { flex: 1 1 auto; min-height: 0; }
|
|
151
|
+
& .review-detail .detail-scroll { flex: 1 1 auto; overflow-y: auto; padding-right: 2px; }
|
|
152
|
+
& .empty-state { margin: auto; max-width: 320px; padding: 24px; text-align: center; }
|
|
153
|
+
& .empty-state h2 { margin: 0 0 6px; font-size: 18px; }
|
|
154
|
+
& .empty-state p { margin: 0; }
|
|
155
|
+
& .mobile-back { display: none; }
|
|
156
|
+
& /* Standalone show pages keep normal page scrolling. */
|
|
137
157
|
.ip-show { min-height: 100vh; overflow: auto; }
|
|
138
|
-
|
|
139
|
-
@media (max-width: 760px) {
|
|
140
|
-
body { font-size: 14px; }
|
|
141
|
-
.container { padding: 14px 10px 24px; }
|
|
142
|
-
.ip-index, .ip-show { overflow-x: hidden; }
|
|
143
|
-
.review-detail, .suggestion-panel, .detail-scroll, .card, dl, dd { min-width: 0; max-width: 100%; }
|
|
144
|
-
.suggestion-panel .card { width: calc(100vw - 20px); }
|
|
145
|
-
.lede, dd, .comment { max-width: calc(100vw - 54px); white-space: normal; overflow-wrap: anywhere; }
|
|
146
|
-
.ip-index { overflow-y: auto; overflow-x: hidden; }
|
|
147
|
-
.ip-index, .ip-index .container { min-height: 100vh; height: auto; }
|
|
148
|
-
.ip-index .container { padding-bottom: 10px; }
|
|
149
|
-
.review-shell { display: block; min-height: calc(100vh - 76px); }
|
|
150
|
-
.review-sidebar, .review-detail { min-height: calc(100vh - 76px); }
|
|
151
|
-
.review-shell.has-selected .review-sidebar { display: none; }
|
|
152
|
-
.review-shell:not(.has-selected) .review-detail { display: none; }
|
|
153
|
-
.suggestion-list { overflow: visible; }
|
|
154
|
-
.suggestion-row { padding: 12px; }
|
|
155
|
-
.suggestion-meta { display: none; }
|
|
156
|
-
.review-detail .suggestion-panel { min-height: calc(100vh - 64px); }
|
|
157
|
-
.mobile-back { display: block; flex: 0 0 auto; margin: 10px 10px 0; font-size: 13px; }
|
|
158
|
-
.panel-head h1 { font-size: 18px; }
|
|
159
|
-
.panel-head .key { min-width: 0; white-space: normal; overflow-wrap: anywhere; }
|
|
160
|
-
dl.diff, .meta-list { grid-template-columns: 1fr; gap: 3px; }
|
|
161
|
-
}
|
|
158
|
+
}
|
|
@@ -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
|
data/lib/i18n_proofreading.rb
CHANGED
|
@@ -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
|
|
@@ -30,6 +31,14 @@ module I18nProofreading
|
|
|
30
31
|
# May this request browse and triage the dashboard? Independent of
|
|
31
32
|
# `available?` — the dashboard has its own gate so maintainers can review
|
|
32
33
|
# suggestions from production even where the widget itself is off.
|
|
34
|
+
# The class I18nProofreading::DashboardController inherits from. Resolved on
|
|
35
|
+
# every call rather than memoized, so a host that reassigns
|
|
36
|
+
# base_controller_class in a reloadable initializer is not pinned to a stale,
|
|
37
|
+
# unloaded constant.
|
|
38
|
+
def base_controller
|
|
39
|
+
config.base_controller_class.to_s.constantize
|
|
40
|
+
end
|
|
41
|
+
|
|
33
42
|
def admin?(request)
|
|
34
43
|
!!config.authorize_admin.call(request)
|
|
35
44
|
end
|
|
@@ -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.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -34,16 +34,21 @@ 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
|
|
40
41
|
- Rakefile
|
|
42
|
+
- app/controllers/concerns/i18n_proofreading/request_context.rb
|
|
41
43
|
- app/controllers/i18n_proofreading/application_controller.rb
|
|
44
|
+
- app/controllers/i18n_proofreading/dashboard_controller.rb
|
|
45
|
+
- app/controllers/i18n_proofreading/submissions_controller.rb
|
|
42
46
|
- app/controllers/i18n_proofreading/suggestions_controller.rb
|
|
43
47
|
- app/controllers/i18n_proofreading/widgets_controller.rb
|
|
44
48
|
- app/helpers/i18n_proofreading/tag_helper.rb
|
|
45
49
|
- app/models/i18n_proofreading/application_record.rb
|
|
46
50
|
- app/models/i18n_proofreading/suggestion.rb
|
|
51
|
+
- app/views/i18n_proofreading/shared/_dashboard.html.erb
|
|
47
52
|
- app/views/i18n_proofreading/suggestions/_suggestion_panel.html.erb
|
|
48
53
|
- app/views/i18n_proofreading/suggestions/index.html.erb
|
|
49
54
|
- app/views/i18n_proofreading/suggestions/show.html.erb
|
|
@@ -78,15 +83,18 @@ files:
|
|
|
78
83
|
- lib/generators/i18n_proofreading/install/install_generator.rb
|
|
79
84
|
- lib/generators/i18n_proofreading/install/templates/create_i18n_proofreading_suggestions.rb.tt
|
|
80
85
|
- lib/generators/i18n_proofreading/install/templates/initializer.rb
|
|
86
|
+
- lib/generators/i18n_proofreading/migration_helpers.rb
|
|
81
87
|
- lib/i18n_proofreading.rb
|
|
82
88
|
- lib/i18n_proofreading/configuration.rb
|
|
83
89
|
- lib/i18n_proofreading/dashboard.css
|
|
84
90
|
- lib/i18n_proofreading/engine.rb
|
|
85
91
|
- lib/i18n_proofreading/marking.rb
|
|
86
92
|
- lib/i18n_proofreading/middleware.rb
|
|
93
|
+
- lib/i18n_proofreading/seeds.rb
|
|
87
94
|
- lib/i18n_proofreading/version.rb
|
|
88
95
|
- lib/i18n_proofreading/widget.js
|
|
89
96
|
- lib/i18n_proofreading/widget.rb
|
|
97
|
+
- lib/tasks/i18n_proofreading_tasks.rake
|
|
90
98
|
homepage: https://github.com/yshmarov/i18n_proofreading
|
|
91
99
|
licenses:
|
|
92
100
|
- MIT
|