ideasbugs 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 +7 -0
- data/CHANGELOG.md +114 -0
- data/MIT-LICENSE +20 -0
- data/README.md +339 -0
- data/Rakefile +19 -0
- data/app/controllers/ideasbugs/application_controller.rb +15 -0
- data/app/controllers/ideasbugs/feedbacks_controller.rb +156 -0
- data/app/controllers/ideasbugs/screenshots_controller.rb +27 -0
- data/app/helpers/ideasbugs/widget_helper.rb +18 -0
- data/app/models/ideasbugs/application_record.rb +7 -0
- data/app/models/ideasbugs/feedback.rb +30 -0
- data/app/views/ideasbugs/feedbacks/index.html.erb +78 -0
- data/app/views/ideasbugs/feedbacks/show.html.erb +63 -0
- data/app/views/layouts/ideasbugs/application.html.erb +76 -0
- data/config/locales/ideasbugs.ar.yml +24 -0
- data/config/locales/ideasbugs.bg.yml +24 -0
- data/config/locales/ideasbugs.bn.yml +24 -0
- data/config/locales/ideasbugs.de.yml +24 -0
- data/config/locales/ideasbugs.el.yml +24 -0
- data/config/locales/ideasbugs.en.yml +47 -0
- data/config/locales/ideasbugs.es.yml +24 -0
- data/config/locales/ideasbugs.fr.yml +24 -0
- data/config/locales/ideasbugs.hi.yml +24 -0
- data/config/locales/ideasbugs.hr.yml +24 -0
- data/config/locales/ideasbugs.id.yml +24 -0
- data/config/locales/ideasbugs.it.yml +24 -0
- data/config/locales/ideasbugs.ja.yml +24 -0
- data/config/locales/ideasbugs.ko.yml +24 -0
- data/config/locales/ideasbugs.lb.yml +24 -0
- data/config/locales/ideasbugs.nl.yml +24 -0
- data/config/locales/ideasbugs.pl.yml +24 -0
- data/config/locales/ideasbugs.pt.yml +24 -0
- data/config/locales/ideasbugs.ro.yml +24 -0
- data/config/locales/ideasbugs.ru.yml +24 -0
- data/config/locales/ideasbugs.th.yml +24 -0
- data/config/locales/ideasbugs.tr.yml +24 -0
- data/config/locales/ideasbugs.uk.yml +24 -0
- data/config/locales/ideasbugs.ur.yml +24 -0
- data/config/locales/ideasbugs.vi.yml +24 -0
- data/config/locales/ideasbugs.zh-CN.yml +24 -0
- data/config/routes.rb +12 -0
- data/lib/generators/ideasbugs/install/install_generator.rb +41 -0
- data/lib/generators/ideasbugs/install/templates/create_ideasbugs_feedbacks.rb.tt +21 -0
- data/lib/generators/ideasbugs/install/templates/initializer.rb +47 -0
- data/lib/ideasbugs/configuration.rb +93 -0
- data/lib/ideasbugs/engine.rb +13 -0
- data/lib/ideasbugs/version.rb +5 -0
- data/lib/ideasbugs/widget.js +387 -0
- data/lib/ideasbugs/widget.rb +109 -0
- data/lib/ideasbugs.rb +34 -0
- metadata +113 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ideasbugs
|
|
4
|
+
# Serves screenshots to the dashboard under its own authorization, streaming
|
|
5
|
+
# the blob instead of linking Active Storage's public blob URLs. Feedback
|
|
6
|
+
# screenshots can contain anything a user's screen showed, so they must never
|
|
7
|
+
# be reachable without passing the same gate as the dashboard — regardless of
|
|
8
|
+
# how the host app configures (or doesn't configure) blob access.
|
|
9
|
+
class ScreenshotsController < ApplicationController
|
|
10
|
+
before_action :require_admin
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
screenshot = Feedback.find(params[:feedback_id]).screenshots.find(params[:id])
|
|
14
|
+
|
|
15
|
+
send_data screenshot.download,
|
|
16
|
+
filename: screenshot.filename.to_s,
|
|
17
|
+
type: screenshot.content_type,
|
|
18
|
+
disposition: 'inline'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def require_admin
|
|
24
|
+
head :forbidden unless Ideasbugs.admin?(request)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ideasbugs
|
|
4
|
+
# Included into the host's ActionView. Drop `<%= ideasbugs_tag %>`
|
|
5
|
+
# before </body> in your layout; it renders nothing unless feedback is
|
|
6
|
+
# enabled for the request.
|
|
7
|
+
module WidgetHelper
|
|
8
|
+
def ideasbugs_tag
|
|
9
|
+
return unless Ideasbugs.enabled?(request)
|
|
10
|
+
|
|
11
|
+
Widget.snippet(
|
|
12
|
+
endpoint: Ideasbugs.config.feedbacks_endpoint,
|
|
13
|
+
locale: I18n.locale,
|
|
14
|
+
nonce: (content_security_policy_nonce if respond_to?(:content_security_policy_nonce))
|
|
15
|
+
).html_safe
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ideasbugs
|
|
4
|
+
# One piece of user feedback. Author attribution is optional and stored as
|
|
5
|
+
# loose fields (no foreign key to the host's user table) so the model is
|
|
6
|
+
# portable across apps with different user models.
|
|
7
|
+
class Feedback < ApplicationRecord
|
|
8
|
+
# Hand-rolled instead of an AR enum: `open` would collide with Kernel#open
|
|
9
|
+
# as a scope name, and three statuses don't need the machinery anyway.
|
|
10
|
+
STATUSES = %w[open in_review resolved].freeze
|
|
11
|
+
|
|
12
|
+
has_many_attached :screenshots if defined?(::ActiveStorage)
|
|
13
|
+
|
|
14
|
+
validates :message, presence: true
|
|
15
|
+
validates :status, inclusion: { in: STATUSES }
|
|
16
|
+
validates :kind,
|
|
17
|
+
presence: true,
|
|
18
|
+
inclusion: { in: ->(_) { Ideasbugs.config.kinds.map(&:to_s) } }
|
|
19
|
+
|
|
20
|
+
scope :newest_first, -> { order(id: :desc) }
|
|
21
|
+
|
|
22
|
+
STATUSES.each do |status|
|
|
23
|
+
define_method(:"#{status}?") { self.status == status }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def screenshots?
|
|
27
|
+
respond_to?(:screenshots) && screenshots.attached?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<h1><%= t('ideasbugs.dashboard.title', default: 'Feedback') %></h1>
|
|
2
|
+
|
|
3
|
+
<div class="tabs">
|
|
4
|
+
<% Ideasbugs::Feedback::STATUSES.each do |status| %>
|
|
5
|
+
<%= link_to feedbacks_path(status: status, kind: @kind, q: @query),
|
|
6
|
+
class: ('active' if status == @status) do %>
|
|
7
|
+
<%= t("ideasbugs.statuses.#{status}", default: status.humanize) %>
|
|
8
|
+
<span class="count"><%= @counts.fetch(status, 0) %></span>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<form class="filters" method="get">
|
|
14
|
+
<input type="hidden" name="status" value="<%= @status %>">
|
|
15
|
+
<select name="kind" onchange="this.form.submit()">
|
|
16
|
+
<option value=""><%= t('ideasbugs.dashboard.all_kinds', default: 'All types') %></option>
|
|
17
|
+
<% Ideasbugs.config.kinds.each do |kind| %>
|
|
18
|
+
<option value="<%= kind %>" <%= 'selected' if kind.to_s == @kind %>>
|
|
19
|
+
<%= t("ideasbugs.kinds.#{kind}", default: kind.to_s.humanize) %>
|
|
20
|
+
</option>
|
|
21
|
+
<% end %>
|
|
22
|
+
</select>
|
|
23
|
+
<input type="search" name="q" value="<%= @query %>"
|
|
24
|
+
placeholder="<%= t('ideasbugs.dashboard.search_placeholder', default: 'Search message, author, section…') %>"
|
|
25
|
+
aria-label="<%= t('ideasbugs.dashboard.search', default: 'Search') %>">
|
|
26
|
+
<button><%= t('ideasbugs.dashboard.search', default: 'Search') %></button>
|
|
27
|
+
</form>
|
|
28
|
+
|
|
29
|
+
<div class="card">
|
|
30
|
+
<% if @feedbacks.any? %>
|
|
31
|
+
<table>
|
|
32
|
+
<thead>
|
|
33
|
+
<tr>
|
|
34
|
+
<th><%= t('ideasbugs.kind', default: 'Type') %></th>
|
|
35
|
+
<th><%= t('ideasbugs.dashboard.message', default: 'Message') %></th>
|
|
36
|
+
<th><%= t('ideasbugs.section', default: 'Section') %></th>
|
|
37
|
+
<th><%= t('ideasbugs.dashboard.from', default: 'From') %></th>
|
|
38
|
+
<th><%= t('ideasbugs.dashboard.filed', default: 'Filed') %></th>
|
|
39
|
+
</tr>
|
|
40
|
+
</thead>
|
|
41
|
+
<tbody>
|
|
42
|
+
<% @feedbacks.each do |feedback| %>
|
|
43
|
+
<tr>
|
|
44
|
+
<td>
|
|
45
|
+
<span class="badge kind-<%= feedback.kind.to_s.parameterize %>">
|
|
46
|
+
<%= t("ideasbugs.kinds.#{feedback.kind}", default: feedback.kind.humanize) %>
|
|
47
|
+
</span>
|
|
48
|
+
</td>
|
|
49
|
+
<td>
|
|
50
|
+
<%= link_to truncate(feedback.message, length: 120), feedback_path(feedback) %>
|
|
51
|
+
<% if feedback.screenshots? %>
|
|
52
|
+
<div class="muted">📎 <%= feedback.screenshots.count %></div>
|
|
53
|
+
<% end %>
|
|
54
|
+
</td>
|
|
55
|
+
<td><%= feedback.section %></td>
|
|
56
|
+
<td><%= feedback.author_label %></td>
|
|
57
|
+
<td class="muted" title="<%= feedback.created_at %>">
|
|
58
|
+
<%= time_ago_in_words(feedback.created_at) %>
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
<% end %>
|
|
62
|
+
</tbody>
|
|
63
|
+
</table>
|
|
64
|
+
<% else %>
|
|
65
|
+
<div class="empty"><%= t('ideasbugs.dashboard.empty', default: 'Nothing here yet.') %></div>
|
|
66
|
+
<% end %>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="pager">
|
|
70
|
+
<% if @page > 1 %>
|
|
71
|
+
<%= link_to t('ideasbugs.dashboard.newer', default: '← Newer'),
|
|
72
|
+
feedbacks_path(status: @status, kind: @kind, q: @query, page: @page - 1) %>
|
|
73
|
+
<% end %>
|
|
74
|
+
<% if @more %>
|
|
75
|
+
<%= link_to t('ideasbugs.dashboard.older', default: 'Older →'),
|
|
76
|
+
feedbacks_path(status: @status, kind: @kind, q: @query, page: @page + 1) %>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<div class="breadcrumb">
|
|
2
|
+
<%= link_to t('ideasbugs.dashboard.title', default: 'Feedback'), root_path %>
|
|
3
|
+
/ #<%= @feedback.id %>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<h1>
|
|
7
|
+
<span class="badge kind-<%= @feedback.kind.to_s.parameterize %>">
|
|
8
|
+
<%= t("ideasbugs.kinds.#{@feedback.kind}", default: @feedback.kind.humanize) %>
|
|
9
|
+
</span>
|
|
10
|
+
<span class="badge status-<%= @feedback.status %>">
|
|
11
|
+
<%= t("ideasbugs.statuses.#{@feedback.status}", default: @feedback.status.humanize) %>
|
|
12
|
+
</span>
|
|
13
|
+
</h1>
|
|
14
|
+
|
|
15
|
+
<div class="card" style="padding: 16px; margin-bottom: 16px;">
|
|
16
|
+
<p class="message"><%= @feedback.message %></p>
|
|
17
|
+
|
|
18
|
+
<% if @feedback.screenshots? %>
|
|
19
|
+
<div class="shots">
|
|
20
|
+
<% @feedback.screenshots.each do |screenshot| %>
|
|
21
|
+
<%= link_to feedback_screenshot_path(@feedback, screenshot.id), target: '_blank', rel: 'noopener' do %>
|
|
22
|
+
<%= image_tag feedback_screenshot_path(@feedback, screenshot.id), alt: screenshot.filename %>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% end %>
|
|
25
|
+
</div>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="card" style="padding: 16px;">
|
|
30
|
+
<dl>
|
|
31
|
+
<% if @feedback.section.present? %>
|
|
32
|
+
<dt><%= t('ideasbugs.section', default: 'Section') %></dt>
|
|
33
|
+
<dd><%= @feedback.section %></dd>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% if @feedback.author_label.present? || @feedback.author_id.present? %>
|
|
36
|
+
<dt><%= t('ideasbugs.dashboard.from', default: 'From') %></dt>
|
|
37
|
+
<dd><%= @feedback.author_label.presence || "##{@feedback.author_id}" %></dd>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% if @feedback.page_url.present? %>
|
|
40
|
+
<dt><%= t('ideasbugs.dashboard.page', default: 'Page') %></dt>
|
|
41
|
+
<dd><%= link_to @feedback.page_url, @feedback.page_url, target: '_blank', rel: 'noopener' %></dd>
|
|
42
|
+
<% end %>
|
|
43
|
+
<% if @feedback.user_agent.present? %>
|
|
44
|
+
<dt><%= t('ideasbugs.dashboard.browser', default: 'Browser') %></dt>
|
|
45
|
+
<dd class="muted"><%= @feedback.user_agent %></dd>
|
|
46
|
+
<% end %>
|
|
47
|
+
<dt><%= t('ideasbugs.dashboard.filed', default: 'Filed') %></dt>
|
|
48
|
+
<dd><%= @feedback.created_at %></dd>
|
|
49
|
+
</dl>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="actions">
|
|
53
|
+
<% (Ideasbugs::Feedback::STATUSES - [@feedback.status]).each do |status| %>
|
|
54
|
+
<%= button_to t("ideasbugs.dashboard.move_to_#{status}", default: "Mark #{status.humanize.downcase}"),
|
|
55
|
+
feedback_path(@feedback),
|
|
56
|
+
method: :patch, params: { feedback: { status: status } },
|
|
57
|
+
class: ('primary' if status == 'resolved') %>
|
|
58
|
+
<% end %>
|
|
59
|
+
<%= button_to t('ideasbugs.dashboard.delete', default: 'Delete'),
|
|
60
|
+
feedback_path(@feedback),
|
|
61
|
+
method: :delete, class: 'danger',
|
|
62
|
+
form: { onsubmit: "return confirm('#{t('ideasbugs.dashboard.delete_confirm', default: 'Delete this feedback?')}')" } %>
|
|
63
|
+
</div>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= t('ideasbugs.dashboard.title', default: 'Feedback') %></title>
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<%= csrf_meta_tags %>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
--bg: #f6f7f9; --surface: #fff; --text: #1c2024; --muted: #6b7280;
|
|
11
|
+
--border: #e5e7eb; --accent: #2563eb; --accent-text: #fff;
|
|
12
|
+
--bug: #dc2626; --feature: #16a34a; --other: #6b7280;
|
|
13
|
+
--open: #2563eb; --in_review: #d97706; --resolved: #16a34a;
|
|
14
|
+
}
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root {
|
|
17
|
+
--bg: #111418; --surface: #1a1f26; --text: #e6e8ea; --muted: #9aa2ab;
|
|
18
|
+
--border: #2a313a; --accent: #3b82f6;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
* { box-sizing: border-box; }
|
|
22
|
+
body { margin: 0; background: var(--bg); color: var(--text);
|
|
23
|
+
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
24
|
+
a { color: var(--accent); text-decoration: none; }
|
|
25
|
+
a:hover { text-decoration: underline; }
|
|
26
|
+
.container { max-width: 960px; margin: 0 auto; padding: 24px 16px 64px; }
|
|
27
|
+
h1 { font-size: 22px; margin: 0 0 16px; }
|
|
28
|
+
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 16px; flex-wrap: wrap; }
|
|
29
|
+
.tabs a { padding: 8px 14px; border-radius: 8px 8px 0 0; color: var(--muted); }
|
|
30
|
+
.tabs a.active { color: var(--text); font-weight: 600; border: 1px solid var(--border);
|
|
31
|
+
border-bottom: 2px solid var(--surface); background: var(--surface); margin-bottom: -1px; }
|
|
32
|
+
.tabs a:hover { text-decoration: none; color: var(--text); }
|
|
33
|
+
.count { 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; }
|
|
35
|
+
.filters { margin-bottom: 16px; display: flex; gap: 8px; align-items: center; }
|
|
36
|
+
.filters select, .filters input[type=search] { padding: 6px 8px; border: 1px solid var(--border);
|
|
37
|
+
border-radius: 8px; background: var(--surface); color: var(--text); font: inherit; }
|
|
38
|
+
.filters input[type=search] { flex: 1; max-width: 320px; }
|
|
39
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
|
|
40
|
+
table { width: 100%; border-collapse: collapse; }
|
|
41
|
+
th, td { padding: 10px 14px; text-align: left; vertical-align: top; }
|
|
42
|
+
th { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
|
|
43
|
+
border-bottom: 1px solid var(--border); }
|
|
44
|
+
tr + tr td { border-top: 1px solid var(--border); }
|
|
45
|
+
.badge { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
|
|
46
|
+
color: #fff; white-space: nowrap; }
|
|
47
|
+
.badge.kind-bug { background: var(--bug); }
|
|
48
|
+
.badge.kind-feature { background: var(--feature); }
|
|
49
|
+
.badge.kind-other, .badge.kind-default { background: var(--other); }
|
|
50
|
+
.badge.status-open { background: var(--open); }
|
|
51
|
+
.badge.status-in_review { background: var(--in_review); }
|
|
52
|
+
.badge.status-resolved { background: var(--resolved); }
|
|
53
|
+
.muted { color: var(--muted); font-size: 13px; }
|
|
54
|
+
.pager { margin-top: 16px; display: flex; gap: 16px; }
|
|
55
|
+
.actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 16px 0; }
|
|
56
|
+
.actions form { display: inline; }
|
|
57
|
+
button, .button { padding: 8px 14px; border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
|
|
58
|
+
background: var(--surface); color: var(--text); font: inherit; }
|
|
59
|
+
button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
|
|
60
|
+
button.danger { color: var(--bug); }
|
|
61
|
+
.message { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
62
|
+
dl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; }
|
|
63
|
+
dt { color: var(--muted); }
|
|
64
|
+
dd { margin: 0; overflow-wrap: anywhere; }
|
|
65
|
+
.shots { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 8px; }
|
|
66
|
+
.shots img { max-width: 280px; max-height: 200px; border: 1px solid var(--border); border-radius: 8px; }
|
|
67
|
+
.empty { padding: 48px 16px; text-align: center; color: var(--muted); }
|
|
68
|
+
.breadcrumb { margin-bottom: 12px; font-size: 13px; }
|
|
69
|
+
</style>
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<div class="container">
|
|
73
|
+
<%= yield %>
|
|
74
|
+
</div>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
ar:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "ملاحظات"
|
|
4
|
+
title: "الإبلاغ عن خطأ / طلب ميزة"
|
|
5
|
+
kind: "النوع"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "الإبلاغ عن خطأ"
|
|
8
|
+
feature: "طلب ميزة"
|
|
9
|
+
other: "أخرى"
|
|
10
|
+
section: "القسم"
|
|
11
|
+
section_any: "عام"
|
|
12
|
+
message: "رسالتك"
|
|
13
|
+
message_placeholder: "أخبرنا بما يدور في ذهنك…"
|
|
14
|
+
screenshots: "لقطات الشاشة"
|
|
15
|
+
screenshots_hint: "اختياري، حتى %{count} ملفات، %{size} ميغابايت لكل ملف"
|
|
16
|
+
submit: "إرسال الملاحظات"
|
|
17
|
+
cancel: "إلغاء"
|
|
18
|
+
close: "إغلاق"
|
|
19
|
+
thanks: "شكرًا لملاحظاتك!"
|
|
20
|
+
error_blank: "يرجى إدخال رسالة."
|
|
21
|
+
error_save: "تعذر إرسال الملاحظات. يرجى المحاولة مرة أخرى."
|
|
22
|
+
error_too_many: "عدد كبير جدًا من لقطات الشاشة (الحد الأقصى %{count})."
|
|
23
|
+
error_too_large: "إحدى لقطات الشاشة كبيرة جدًا (الحد الأقصى %{size} ميغابايت)."
|
|
24
|
+
error_rate_limited: "عدد كبير جدًا من الإرسالات. يرجى الانتظار قليلًا والمحاولة مرة أخرى."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
bg:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Обратна връзка"
|
|
4
|
+
title: "Докладване на грешка / заявка за функция"
|
|
5
|
+
kind: "Тип"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Докладване на грешка"
|
|
8
|
+
feature: "Заявка за функционалност"
|
|
9
|
+
other: "Друго"
|
|
10
|
+
section: "Раздел"
|
|
11
|
+
section_any: "Общи"
|
|
12
|
+
message: "Вашето съобщение"
|
|
13
|
+
message_placeholder: "Кажете ни какво мислите…"
|
|
14
|
+
screenshots: "Екранни снимки"
|
|
15
|
+
screenshots_hint: "по избор, до %{count} файла, по %{size} MB всеки"
|
|
16
|
+
submit: "Изпрати"
|
|
17
|
+
cancel: "Отказ"
|
|
18
|
+
close: "Затвори"
|
|
19
|
+
thanks: "Благодарим за обратната връзка!"
|
|
20
|
+
error_blank: "Моля, въведете съобщение."
|
|
21
|
+
error_save: "Изпращането не бе успешно. Моля, опитайте отново."
|
|
22
|
+
error_too_many: "Твърде много екранни снимки (макс. %{count})."
|
|
23
|
+
error_too_large: "Екранна снимка е твърде голяма (макс. %{size} MB)."
|
|
24
|
+
error_rate_limited: "Твърде много изпращания. Изчакайте малко и опитайте отново."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
bn:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "মতামত"
|
|
4
|
+
title: "ত্রুটি জানান / ফিচারের অনুরোধ করুন"
|
|
5
|
+
kind: "ধরন"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "ত্রুটি জানান"
|
|
8
|
+
feature: "ফিচারের অনুরোধ"
|
|
9
|
+
other: "অন্যান্য"
|
|
10
|
+
section: "বিভাগ"
|
|
11
|
+
section_any: "সাধারণ"
|
|
12
|
+
message: "আপনার বার্তা"
|
|
13
|
+
message_placeholder: "আপনার মনে কী আছে আমাদের জানান…"
|
|
14
|
+
screenshots: "স্ক্রিনশট"
|
|
15
|
+
screenshots_hint: "ঐচ্ছিক, সর্বোচ্চ %{count}টি ফাইল, প্রতিটি %{size} MB"
|
|
16
|
+
submit: "মতামত পাঠান"
|
|
17
|
+
cancel: "বাতিল"
|
|
18
|
+
close: "বন্ধ করুন"
|
|
19
|
+
thanks: "আপনার মতামতের জন্য ধন্যবাদ!"
|
|
20
|
+
error_blank: "অনুগ্রহ করে একটি বার্তা লিখুন।"
|
|
21
|
+
error_save: "মতামত পাঠানো যায়নি। আবার চেষ্টা করুন।"
|
|
22
|
+
error_too_many: "অতিরিক্ত স্ক্রিনশট (সর্বোচ্চ %{count}টি)।"
|
|
23
|
+
error_too_large: "একটি স্ক্রিনশট খুব বড় (সর্বোচ্চ %{size} MB)।"
|
|
24
|
+
error_rate_limited: "অনেক বেশি জমা হয়েছে। একটু অপেক্ষা করে আবার চেষ্টা করুন।"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
de:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Feedback"
|
|
4
|
+
title: "Fehler melden / Funktion vorschlagen"
|
|
5
|
+
kind: "Art"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Fehlerbericht"
|
|
8
|
+
feature: "Funktionswunsch"
|
|
9
|
+
other: "Sonstiges"
|
|
10
|
+
section: "Bereich"
|
|
11
|
+
section_any: "Allgemein"
|
|
12
|
+
message: "Ihre Nachricht"
|
|
13
|
+
message_placeholder: "Sagen Sie uns, was Sie denken…"
|
|
14
|
+
screenshots: "Screenshots"
|
|
15
|
+
screenshots_hint: "optional, bis zu %{count} Dateien, je %{size} MB"
|
|
16
|
+
submit: "Feedback senden"
|
|
17
|
+
cancel: "Abbrechen"
|
|
18
|
+
close: "Schließen"
|
|
19
|
+
thanks: "Danke für Ihr Feedback!"
|
|
20
|
+
error_blank: "Bitte geben Sie eine Nachricht ein."
|
|
21
|
+
error_save: "Senden fehlgeschlagen. Bitte erneut versuchen."
|
|
22
|
+
error_too_many: "Zu viele Screenshots (max. %{count})."
|
|
23
|
+
error_too_large: "Ein Screenshot ist zu groß (max. %{size} MB)."
|
|
24
|
+
error_rate_limited: "Zu viele Einsendungen. Bitte warten Sie einen Moment und versuchen Sie es erneut."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
el:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Σχόλια"
|
|
4
|
+
title: "Αναφορά σφάλματος / αίτημα λειτουργίας"
|
|
5
|
+
kind: "Τύπος"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Αναφορά σφάλματος"
|
|
8
|
+
feature: "Αίτημα λειτουργίας"
|
|
9
|
+
other: "Άλλο"
|
|
10
|
+
section: "Ενότητα"
|
|
11
|
+
section_any: "Γενικά"
|
|
12
|
+
message: "Το μήνυμά σας"
|
|
13
|
+
message_placeholder: "Πείτε μας τι σκέφτεστε…"
|
|
14
|
+
screenshots: "Στιγμιότυπα οθόνης"
|
|
15
|
+
screenshots_hint: "προαιρετικά, έως %{count} αρχεία, %{size} MB το καθένα"
|
|
16
|
+
submit: "Αποστολή σχολίων"
|
|
17
|
+
cancel: "Άκυρο"
|
|
18
|
+
close: "Κλείσιμο"
|
|
19
|
+
thanks: "Ευχαριστούμε για τα σχόλιά σας!"
|
|
20
|
+
error_blank: "Παρακαλώ εισαγάγετε ένα μήνυμα."
|
|
21
|
+
error_save: "Η αποστολή απέτυχε. Δοκιμάστε ξανά."
|
|
22
|
+
error_too_many: "Πάρα πολλά στιγμιότυπα οθόνης (έως %{count})."
|
|
23
|
+
error_too_large: "Ένα στιγμιότυπο οθόνης είναι πολύ μεγάλο (έως %{size} MB)."
|
|
24
|
+
error_rate_limited: "Πάρα πολλές υποβολές. Περιμένετε λίγο και δοκιμάστε ξανά."
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
en:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Feedback"
|
|
4
|
+
title: "Send bug/feature request"
|
|
5
|
+
kind: "Type"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Bug report"
|
|
8
|
+
feature: "Feature request"
|
|
9
|
+
other: "Other"
|
|
10
|
+
section: "Section"
|
|
11
|
+
section_any: "General"
|
|
12
|
+
message: "Your message"
|
|
13
|
+
message_placeholder: "Tell us what's on your mind…"
|
|
14
|
+
screenshots: "Screenshots"
|
|
15
|
+
screenshots_hint: "optional, up to %{count} files, %{size} MB each"
|
|
16
|
+
submit: "Send feedback"
|
|
17
|
+
cancel: "Cancel"
|
|
18
|
+
close: "Close"
|
|
19
|
+
thanks: "Thanks for your feedback!"
|
|
20
|
+
error_blank: "Please enter a message."
|
|
21
|
+
error_save: "Could not send feedback. Please try again."
|
|
22
|
+
error_too_many: "Too many screenshots (max %{count})."
|
|
23
|
+
error_too_large: "A screenshot is too large (max %{size} MB)."
|
|
24
|
+
error_rate_limited: "Too many submissions. Please wait a moment and try again."
|
|
25
|
+
statuses:
|
|
26
|
+
open: "Open"
|
|
27
|
+
in_review: "In review"
|
|
28
|
+
resolved: "Resolved"
|
|
29
|
+
dashboard:
|
|
30
|
+
title: "Feedback"
|
|
31
|
+
all_kinds: "All types"
|
|
32
|
+
filter: "Filter"
|
|
33
|
+
message: "Message"
|
|
34
|
+
search: "Search"
|
|
35
|
+
search_placeholder: "Search message, author, section…"
|
|
36
|
+
from: "From"
|
|
37
|
+
filed: "Filed"
|
|
38
|
+
page: "Page"
|
|
39
|
+
browser: "Browser"
|
|
40
|
+
empty: "Nothing here yet."
|
|
41
|
+
newer: "← Newer"
|
|
42
|
+
older: "Older →"
|
|
43
|
+
move_to_open: "Reopen"
|
|
44
|
+
move_to_in_review: "Mark in review"
|
|
45
|
+
move_to_resolved: "Mark resolved"
|
|
46
|
+
delete: "Delete"
|
|
47
|
+
delete_confirm: "Delete this feedback?"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
es:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Comentarios"
|
|
4
|
+
title: "Reportar un error / pedir una función"
|
|
5
|
+
kind: "Tipo"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Informe de error"
|
|
8
|
+
feature: "Solicitud de función"
|
|
9
|
+
other: "Otro"
|
|
10
|
+
section: "Sección"
|
|
11
|
+
section_any: "General"
|
|
12
|
+
message: "Tu mensaje"
|
|
13
|
+
message_placeholder: "Cuéntanos qué piensas…"
|
|
14
|
+
screenshots: "Capturas de pantalla"
|
|
15
|
+
screenshots_hint: "opcional, hasta %{count} archivos, %{size} MB cada uno"
|
|
16
|
+
submit: "Enviar comentarios"
|
|
17
|
+
cancel: "Cancelar"
|
|
18
|
+
close: "Cerrar"
|
|
19
|
+
thanks: "¡Gracias por tus comentarios!"
|
|
20
|
+
error_blank: "Por favor escribe un mensaje."
|
|
21
|
+
error_save: "No se pudo enviar. Inténtalo de nuevo."
|
|
22
|
+
error_too_many: "Demasiadas capturas (máx. %{count})."
|
|
23
|
+
error_too_large: "Una captura es demasiado grande (máx. %{size} MB)."
|
|
24
|
+
error_rate_limited: "Demasiados envíos. Espera un momento e inténtalo de nuevo."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
fr:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Commentaires"
|
|
4
|
+
title: "Signaler un bug / demander une fonctionnalité"
|
|
5
|
+
kind: "Type"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Signaler un bug"
|
|
8
|
+
feature: "Suggestion de fonctionnalité"
|
|
9
|
+
other: "Autre"
|
|
10
|
+
section: "Section"
|
|
11
|
+
section_any: "Général"
|
|
12
|
+
message: "Votre message"
|
|
13
|
+
message_placeholder: "Dites-nous ce que vous pensez…"
|
|
14
|
+
screenshots: "Captures d'écran"
|
|
15
|
+
screenshots_hint: "facultatif, jusqu'à %{count} fichiers, %{size} Mo chacun"
|
|
16
|
+
submit: "Envoyer"
|
|
17
|
+
cancel: "Annuler"
|
|
18
|
+
close: "Fermer"
|
|
19
|
+
thanks: "Merci pour votre retour !"
|
|
20
|
+
error_blank: "Veuillez saisir un message."
|
|
21
|
+
error_save: "Envoi impossible. Veuillez réessayer."
|
|
22
|
+
error_too_many: "Trop de captures d'écran (max %{count})."
|
|
23
|
+
error_too_large: "Une capture est trop volumineuse (max %{size} Mo)."
|
|
24
|
+
error_rate_limited: "Trop d'envois. Veuillez patienter un instant et réessayer."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
hi:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "प्रतिक्रिया"
|
|
4
|
+
title: "बग रिपोर्ट / फ़ीचर अनुरोध भेजें"
|
|
5
|
+
kind: "प्रकार"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "बग रिपोर्ट"
|
|
8
|
+
feature: "फ़ीचर अनुरोध"
|
|
9
|
+
other: "अन्य"
|
|
10
|
+
section: "अनुभाग"
|
|
11
|
+
section_any: "सामान्य"
|
|
12
|
+
message: "आपका संदेश"
|
|
13
|
+
message_placeholder: "हमें बताएं कि आप क्या सोच रहे हैं…"
|
|
14
|
+
screenshots: "स्क्रीनशॉट"
|
|
15
|
+
screenshots_hint: "वैकल्पिक, अधिकतम %{count} फ़ाइलें, प्रत्येक %{size} MB"
|
|
16
|
+
submit: "प्रतिक्रिया भेजें"
|
|
17
|
+
cancel: "रद्द करें"
|
|
18
|
+
close: "बंद करें"
|
|
19
|
+
thanks: "आपकी प्रतिक्रिया के लिए धन्यवाद!"
|
|
20
|
+
error_blank: "कृपया एक संदेश लिखें।"
|
|
21
|
+
error_save: "प्रतिक्रिया नहीं भेजी जा सकी। कृपया फिर से प्रयास करें।"
|
|
22
|
+
error_too_many: "बहुत अधिक स्क्रीनशॉट (अधिकतम %{count})।"
|
|
23
|
+
error_too_large: "एक स्क्रीनशॉट बहुत बड़ा है (अधिकतम %{size} MB)।"
|
|
24
|
+
error_rate_limited: "बहुत अधिक सबमिशन। कृपया थोड़ी देर रुककर फिर से प्रयास करें।"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
hr:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Povratne informacije"
|
|
4
|
+
title: "Prijavi grešku / predloži značajku"
|
|
5
|
+
kind: "Vrsta"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Prijava greške"
|
|
8
|
+
feature: "Zahtjev za značajku"
|
|
9
|
+
other: "Ostalo"
|
|
10
|
+
section: "Odjeljak"
|
|
11
|
+
section_any: "Općenito"
|
|
12
|
+
message: "Vaša poruka"
|
|
13
|
+
message_placeholder: "Recite nam što mislite…"
|
|
14
|
+
screenshots: "Snimke zaslona"
|
|
15
|
+
screenshots_hint: "neobavezno, do %{count} datoteke, svaka %{size} MB"
|
|
16
|
+
submit: "Pošalji"
|
|
17
|
+
cancel: "Odustani"
|
|
18
|
+
close: "Zatvori"
|
|
19
|
+
thanks: "Hvala na povratnim informacijama!"
|
|
20
|
+
error_blank: "Molimo unesite poruku."
|
|
21
|
+
error_save: "Slanje nije uspjelo. Pokušajte ponovno."
|
|
22
|
+
error_too_many: "Previše snimki zaslona (najviše %{count})."
|
|
23
|
+
error_too_large: "Snimka zaslona je prevelika (najviše %{size} MB)."
|
|
24
|
+
error_rate_limited: "Previše slanja. Pričekajte trenutak i pokušajte ponovno."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
id:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Masukan"
|
|
4
|
+
title: "Kirim laporan bug / permintaan fitur"
|
|
5
|
+
kind: "Jenis"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Laporan bug"
|
|
8
|
+
feature: "Permintaan fitur"
|
|
9
|
+
other: "Lainnya"
|
|
10
|
+
section: "Bagian"
|
|
11
|
+
section_any: "Umum"
|
|
12
|
+
message: "Pesan Anda"
|
|
13
|
+
message_placeholder: "Ceritakan apa yang Anda pikirkan…"
|
|
14
|
+
screenshots: "Tangkapan layar"
|
|
15
|
+
screenshots_hint: "opsional, maksimal %{count} berkas, masing-masing %{size} MB"
|
|
16
|
+
submit: "Kirim masukan"
|
|
17
|
+
cancel: "Batal"
|
|
18
|
+
close: "Tutup"
|
|
19
|
+
thanks: "Terima kasih atas masukan Anda!"
|
|
20
|
+
error_blank: "Silakan tulis pesan."
|
|
21
|
+
error_save: "Masukan gagal terkirim. Silakan coba lagi."
|
|
22
|
+
error_too_many: "Terlalu banyak tangkapan layar (maks. %{count})."
|
|
23
|
+
error_too_large: "Sebuah tangkapan layar terlalu besar (maks. %{size} MB)."
|
|
24
|
+
error_rate_limited: "Terlalu banyak kiriman. Tunggu sebentar dan coba lagi."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
it:
|
|
2
|
+
ideasbugs:
|
|
3
|
+
button: "Feedback"
|
|
4
|
+
title: "Segnala un errore / richiedi una funzionalità"
|
|
5
|
+
kind: "Tipo"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Segnala un errore"
|
|
8
|
+
feature: "Richiesta di funzionalità"
|
|
9
|
+
other: "Altro"
|
|
10
|
+
section: "Sezione"
|
|
11
|
+
section_any: "Generale"
|
|
12
|
+
message: "Il tuo messaggio"
|
|
13
|
+
message_placeholder: "Dicci cosa ne pensi…"
|
|
14
|
+
screenshots: "Screenshot"
|
|
15
|
+
screenshots_hint: "facoltativo, fino a %{count} file, %{size} MB ciascuno"
|
|
16
|
+
submit: "Invia feedback"
|
|
17
|
+
cancel: "Annulla"
|
|
18
|
+
close: "Chiudi"
|
|
19
|
+
thanks: "Grazie per il tuo feedback!"
|
|
20
|
+
error_blank: "Inserisci un messaggio."
|
|
21
|
+
error_save: "Invio non riuscito. Riprova."
|
|
22
|
+
error_too_many: "Troppi screenshot (max %{count})."
|
|
23
|
+
error_too_large: "Uno screenshot è troppo grande (max %{size} MB)."
|
|
24
|
+
error_rate_limited: "Troppi invii. Attendi un momento e riprova."
|