testimonials 0.7.7 → 0.7.9
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 +28 -0
- data/app/controllers/testimonials/nps_responses_controller.rb +8 -0
- data/app/models/testimonials/nps_response.rb +17 -0
- data/app/views/testimonials/nps_responses/index.html.erb +81 -6
- data/config/locales/testimonials.ar.yml +12 -0
- data/config/locales/testimonials.bg.yml +12 -0
- data/config/locales/testimonials.bn.yml +12 -0
- data/config/locales/testimonials.de.yml +12 -0
- data/config/locales/testimonials.el.yml +12 -0
- data/config/locales/testimonials.en.yml +12 -0
- data/config/locales/testimonials.es.yml +12 -0
- data/config/locales/testimonials.fr.yml +12 -0
- data/config/locales/testimonials.hi.yml +12 -0
- data/config/locales/testimonials.hr.yml +12 -0
- data/config/locales/testimonials.id.yml +12 -0
- data/config/locales/testimonials.it.yml +12 -0
- data/config/locales/testimonials.ja.yml +12 -0
- data/config/locales/testimonials.ko.yml +12 -0
- data/config/locales/testimonials.lb.yml +12 -0
- data/config/locales/testimonials.nl.yml +12 -0
- data/config/locales/testimonials.pl.yml +12 -0
- data/config/locales/testimonials.pt.yml +12 -0
- data/config/locales/testimonials.ro.yml +12 -0
- data/config/locales/testimonials.ru.yml +12 -0
- data/config/locales/testimonials.th.yml +12 -0
- data/config/locales/testimonials.tr.yml +12 -0
- data/config/locales/testimonials.uk.yml +12 -0
- data/config/locales/testimonials.ur.yml +12 -0
- data/config/locales/testimonials.vi.yml +12 -0
- data/config/locales/testimonials.zh-CN.yml +12 -0
- data/lib/generators/testimonials/install/install_generator.rb +13 -2
- data/lib/generators/testimonials/install/templates/create_testimonials_tables.rb.tt +2 -0
- data/lib/generators/testimonials/install/templates/{initializer.rb → initializer.rb.tt} +8 -0
- data/lib/generators/testimonials/nps/nps_generator.rb +37 -0
- data/lib/generators/testimonials/nps/templates/create_testimonials_nps_responses.rb.tt +20 -0
- data/lib/generators/testimonials/tenant/templates/add_tenant_to_testimonials.rb.tt +6 -2
- data/lib/testimonials/dashboard.css +51 -0
- data/lib/testimonials/dashboard.js +16 -0
- data/lib/testimonials/seeds.rb +2 -1
- data/lib/testimonials/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 746c0b7bc1421a8aadfbbbd7e9d41cb4aabc81ff0c6afa60669e4d9c06e963b0
|
|
4
|
+
data.tar.gz: 3914ba53cef25fcb6f541e6b4176e1d6135205cb37cdcc05b289125d456f1bbb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 55f8694212e3f1395481121efd0f67c8474e9422173ba63e9b05cf66f40320b340301a1be39537cfbeb8ce17292b289d4f73247b2a05742e52f79b1bad6d47ae
|
|
7
|
+
data.tar.gz: 1a57a1aa934adc3d3b92199d922effc5ec36d66dc9138f819b1b153f754431c5e5bd7bea4f21e65fe51bb725d732e648acf7f389f8168ac277f174e220130b19
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.9
|
|
4
|
+
|
|
5
|
+
- NPS is now optional at install time. `bin/rails generate testimonials:install
|
|
6
|
+
--skip-nps` leaves out the `testimonials_nps_responses` table and writes
|
|
7
|
+
`config.nps = false`, so an app that only wants testimonials carries neither
|
|
8
|
+
the table nor the NPS tab. `bin/rails generate testimonials:nps` adds the
|
|
9
|
+
table later, mirroring the `testimonials:tenant` upgrade path.
|
|
10
|
+
- The guard is `config.nps`, not table introspection — nothing checks the
|
|
11
|
+
schema at runtime and nothing touches the database at boot. Consequently
|
|
12
|
+
`/testimonials/nps_responses` now 404s when `config.nps` is false instead of
|
|
13
|
+
still serving history; flip the flag back to read old responses. The
|
|
14
|
+
dashboard nav already hid the tab, so only a typed URL reached it.
|
|
15
|
+
- `testimonials:seed_demo` skips NPS rows when the flow is off, and the
|
|
16
|
+
`testimonials:tenant` migration skips the NPS table when it isn't there.
|
|
17
|
+
|
|
18
|
+
## 0.7.8
|
|
19
|
+
|
|
20
|
+
- The NPS dashboard now says what the score means. The card carries the band
|
|
21
|
+
the number falls into (needs work / good / great / excellent), a scale
|
|
22
|
+
showing where it sits between −100 and +100, and a note when fewer than 30
|
|
23
|
+
responses make the number too thin to read. A collapsed "How is this
|
|
24
|
+
calculated, and what is a good score?" panel spells out the formula, the four
|
|
25
|
+
bands, and the caveats — industry, trend, comments. Translated into all 26
|
|
26
|
+
locales.
|
|
27
|
+
- The marker's position rides on a `data-nps-marker` attribute that
|
|
28
|
+
`dashboard.js` reads, so the page still ships no inline styles for a strict
|
|
29
|
+
`style-src` to refuse.
|
|
30
|
+
|
|
3
31
|
## 0.7.7
|
|
4
32
|
|
|
5
33
|
- Shrank the gem from 1.0 MB back to ~90 KB by dropping the bundled 1.1 MB demo
|
data/README.md
CHANGED
|
@@ -33,6 +33,15 @@ bin/rails db:migrate
|
|
|
33
33
|
The generator writes the initializer, the migration, and mounts the engine at
|
|
34
34
|
`/testimonials`. Nothing renders until the widget is opened.
|
|
35
35
|
|
|
36
|
+
Don't want NPS? Install without it — no table, no dashboard tab, no prompt:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bin/rails generate testimonials:install --skip-nps
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Changed your mind later: `bin/rails generate testimonials:nps`, migrate, and
|
|
43
|
+
set `config.nps = true`.
|
|
44
|
+
|
|
36
45
|
Optional demo data:
|
|
37
46
|
|
|
38
47
|
```bash
|
|
@@ -242,10 +251,29 @@ throttle would allow it. Detractors (0–6) trigger `on_detractor`, which pairs
|
|
|
242
251
|
well with [ideasbugs](https://github.com/yshmarov/ideasbugs). Your score and
|
|
243
252
|
every response live at `/testimonials/nps_responses`.
|
|
244
253
|
|
|
254
|
+
| 1. One question, eleven taps | 2. The reason, optional |
|
|
255
|
+
| --- | --- |
|
|
256
|
+
|  |  |
|
|
257
|
+
| Ten seconds of work, and "Not now" is always there. | The comment is where the actionable half lives. Promoters go straight on to the testimonial form. |
|
|
258
|
+
|
|
259
|
+
The score is `% promoters − % detractors`, from −100 to +100, and the dashboard
|
|
260
|
+
reads it against the usual benchmarks so nobody has to go and google them:
|
|
261
|
+
below 0 needs work, 0–29 is good, 30–69 is great, 70 and up is world-class. A
|
|
262
|
+
scale under the number shows where you sit, and under 30 responses the card
|
|
263
|
+
says the sample is too thin to read much into.
|
|
264
|
+
|
|
265
|
+
<img src="https://raw.githubusercontent.com/yshmarov/testimonials/main/docs/screenshots/nps-dashboard.jpg" alt="The NPS dashboard: a score of 41 badged Great, a scale marking where it sits between −100 and +100, promoter/passive/detractor counts, and the collapsed panel explaining the formula and the benchmarks" width="820">
|
|
266
|
+
|
|
245
267
|
`/testimonials/nps/new` is the same question on its own page — the link for an
|
|
246
268
|
email campaign, where there is no app session to prompt inside of. A promoter
|
|
247
269
|
who scores 9–10 gets the testimonial form right there on the page.
|
|
248
270
|
|
|
271
|
+
NPS is optional. `config.nps = false` — what `--skip-nps` writes at install —
|
|
272
|
+
turns off the prompt, the public page, the `/api/stats` score and the dashboard
|
|
273
|
+
tab, and `/testimonials/nps_responses` then 404s. Turning it off on an install
|
|
274
|
+
that has the table hides existing responses along with everything else; flip it
|
|
275
|
+
back to read them.
|
|
276
|
+
|
|
249
277
|
## The public pages
|
|
250
278
|
|
|
251
279
|
Two standalone, self-styled pages for people outside the app — drop either link
|
|
@@ -8,11 +8,15 @@ module Testimonials
|
|
|
8
8
|
layout :testimonials_admin_layout
|
|
9
9
|
|
|
10
10
|
before_action :require_admin
|
|
11
|
+
# An install run with --skip-nps has no table behind these pages, and the
|
|
12
|
+
# nav already hides the tab whenever config.nps is off.
|
|
13
|
+
before_action :require_nps
|
|
11
14
|
before_action :set_response, only: :show
|
|
12
15
|
|
|
13
16
|
def index
|
|
14
17
|
scope = NpsResponse.for_tenant(current_tenant)
|
|
15
18
|
@score = NpsResponse.score(scope)
|
|
19
|
+
@band = NpsResponse.band(@score)
|
|
16
20
|
@total = scope.count
|
|
17
21
|
@promoters = scope.where(score: 9..10).count
|
|
18
22
|
@passives = scope.where(score: 7..8).count
|
|
@@ -30,6 +34,10 @@ module Testimonials
|
|
|
30
34
|
|
|
31
35
|
private
|
|
32
36
|
|
|
37
|
+
def require_nps
|
|
38
|
+
head :not_found unless Testimonials.config.nps
|
|
39
|
+
end
|
|
40
|
+
|
|
33
41
|
def set_response
|
|
34
42
|
@response = NpsResponse.for_tenant(current_tenant).find(params[:id])
|
|
35
43
|
end
|
|
@@ -21,5 +21,22 @@ module Testimonials
|
|
|
21
21
|
detractors = scope.where(score: 0..6).count
|
|
22
22
|
((promoters - detractors) * 100.0 / total).round
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
# The industry-standard reading of an NPS number, so the dashboard can say
|
|
26
|
+
# whether a score is good without anyone having to go and google it.
|
|
27
|
+
def self.band(nps)
|
|
28
|
+
return nil if nps.nil?
|
|
29
|
+
|
|
30
|
+
case nps
|
|
31
|
+
when ...0 then :needs_work
|
|
32
|
+
when 0...30 then :good
|
|
33
|
+
when 30...70 then :great
|
|
34
|
+
else :excellent
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Below this many responses a single answer swings the score by several
|
|
39
|
+
# points, so the dashboard says so instead of implying the number is solid.
|
|
40
|
+
THIN_SAMPLE = 30
|
|
24
41
|
end
|
|
25
42
|
end
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="page-head">
|
|
4
4
|
<h1><%= t('testimonials.dashboard.nps', default: 'NPS') %></h1>
|
|
5
|
-
<%# The shareable NPS page, same idea as the testimonial one.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<% if Testimonials.config.public_collection
|
|
5
|
+
<%# The shareable NPS page, same idea as the testimonial one. config.nps is
|
|
6
|
+
already true here — the controller 404s otherwise — so only the public
|
|
7
|
+
switch is left to check. %>
|
|
8
|
+
<% if Testimonials.config.public_collection %>
|
|
9
9
|
<a class="button share-link" href="<%= nps_collection_url %>" target="_blank" rel="noopener"
|
|
10
10
|
title="<%= nps_collection_url %>">
|
|
11
11
|
🔗 <%= t('testimonials.dashboard.nps_page', default: 'Ask for NPS scores') %>
|
|
@@ -13,10 +13,59 @@
|
|
|
13
13
|
<% end %>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
+
<%# How the industry reads a score, so nobody has to go and google it. Range
|
|
17
|
+
labels are digits on purpose — no translation needed for "< 0". %>
|
|
18
|
+
<% bands = [
|
|
19
|
+
{ key: 'needs-work', range: '< 0',
|
|
20
|
+
name: t('testimonials.dashboard.nps_band_needs_work', default: 'Needs work'),
|
|
21
|
+
note: t('testimonials.dashboard.nps_guide_needs_work', default: 'More detractors than promoters.') },
|
|
22
|
+
{ key: 'good', range: '0 – 29',
|
|
23
|
+
name: t('testimonials.dashboard.nps_band_good', default: 'Good'),
|
|
24
|
+
note: t('testimonials.dashboard.nps_guide_good', default: 'Where most companies land.') },
|
|
25
|
+
{ key: 'great', range: '30 – 69',
|
|
26
|
+
name: t('testimonials.dashboard.nps_band_great', default: 'Great'),
|
|
27
|
+
note: t('testimonials.dashboard.nps_guide_great', default: 'Strong loyalty.') },
|
|
28
|
+
{ key: 'excellent', range: '70 +',
|
|
29
|
+
name: t('testimonials.dashboard.nps_band_excellent', default: 'Excellent'),
|
|
30
|
+
note: t('testimonials.dashboard.nps_guide_excellent', default: 'World-class, and rare.') }
|
|
31
|
+
] %>
|
|
32
|
+
<% current = bands.find { |b| b[:key] == @band.to_s.tr('_', '-') } if @band %>
|
|
33
|
+
<% if current
|
|
34
|
+
band_class = "badge nps-band-#{current[:key]}"
|
|
35
|
+
meter_label = "#{@score} — #{current[:name]} #{current[:note]}"
|
|
36
|
+
end %>
|
|
37
|
+
|
|
16
38
|
<div class="stat-row">
|
|
17
|
-
<div class="stat">
|
|
18
|
-
<
|
|
39
|
+
<div class="stat stat-nps">
|
|
40
|
+
<div class="stat-headline">
|
|
41
|
+
<b><%= @score.nil? ? '—' : @score %></b>
|
|
42
|
+
<% if current %>
|
|
43
|
+
<span class="<%= band_class %>"><%= current[:name] %></span>
|
|
44
|
+
<% end %>
|
|
45
|
+
</div>
|
|
19
46
|
<span><%= t('testimonials.dashboard.nps_score', default: 'NPS score') %></span>
|
|
47
|
+
<%# Where this score sits on the −100..100 scale, so the number reads as
|
|
48
|
+
good or bad at a glance. Kept LTR: it is a number line, not prose. %>
|
|
49
|
+
<% if current %>
|
|
50
|
+
<div class="nps-meter" role="img" aria-label="<%= meter_label %>">
|
|
51
|
+
<div class="nps-meter-track">
|
|
52
|
+
<i class="nps-seg nps-band-needs-work"></i><i class="nps-seg nps-band-good"></i>
|
|
53
|
+
<i class="nps-seg nps-band-great"></i><i class="nps-seg nps-band-excellent"></i>
|
|
54
|
+
<%# Position is data, not markup: dashboard.js places it, so a strict
|
|
55
|
+
style-src has no inline style to refuse. %>
|
|
56
|
+
<i class="nps-marker" data-nps-marker="<%= @score %>"></i>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="nps-meter-ticks" aria-hidden="true">
|
|
59
|
+
<span class="tick-min">−100</span><span class="tick-zero">0</span>
|
|
60
|
+
<span class="tick-good">30</span><span class="tick-great">70</span>
|
|
61
|
+
<span class="tick-max">100</span>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<% end %>
|
|
65
|
+
<% if @total.positive? && @total < Testimonials::NpsResponse::THIN_SAMPLE %>
|
|
66
|
+
<p class="muted nps-sample"><%= t('testimonials.dashboard.nps_sample_note', count: @total,
|
|
67
|
+
default: 'Based on %{count} responses — too few to read much into.') %></p>
|
|
68
|
+
<% end %>
|
|
20
69
|
</div>
|
|
21
70
|
<div class="stat">
|
|
22
71
|
<b><%= @promoters %></b>
|
|
@@ -32,6 +81,32 @@
|
|
|
32
81
|
</div>
|
|
33
82
|
</div>
|
|
34
83
|
|
|
84
|
+
<%# The formula and the benchmarks, closed by default — there when someone
|
|
85
|
+
wonders whether their number is any good, out of the way the rest of the time. %>
|
|
86
|
+
<details class="nps-guide">
|
|
87
|
+
<summary><%= t('testimonials.dashboard.nps_guide', default: 'How is this calculated, and what is a good score?') %></summary>
|
|
88
|
+
<div class="nps-guide-body">
|
|
89
|
+
<p><%= t('testimonials.dashboard.nps_guide_formula',
|
|
90
|
+
default: 'NPS = the % of promoters minus the % of detractors. Passives count in the total but on ' \
|
|
91
|
+
'neither side, so they pull the score toward zero. The result runs from −100 to +100 — ' \
|
|
92
|
+
'a score, not a percentage.') %></p>
|
|
93
|
+
<ul class="nps-bands">
|
|
94
|
+
<% bands.each do |band| %>
|
|
95
|
+
<% badge_class = "badge nps-band-#{band[:key]}" %>
|
|
96
|
+
<li>
|
|
97
|
+
<span class="<%= badge_class %>"><%= band[:name] %></span>
|
|
98
|
+
<b><%= band[:range] %></b>
|
|
99
|
+
<span class="muted"><%= band[:note] %></span>
|
|
100
|
+
</li>
|
|
101
|
+
<% end %>
|
|
102
|
+
</ul>
|
|
103
|
+
<p class="muted"><%= t('testimonials.dashboard.nps_guide_caveat',
|
|
104
|
+
default: 'Benchmarks move with the industry — software often sits near 40, airlines ' \
|
|
105
|
+
'and internet providers near 0. The trend over time tells you more than any ' \
|
|
106
|
+
'single number, and the comments tell you more than the score.') %></p>
|
|
107
|
+
</div>
|
|
108
|
+
</details>
|
|
109
|
+
|
|
35
110
|
<div class="dashboard-shell <%= 'has-selected' if @selected_response %>">
|
|
36
111
|
<aside class="dashboard-sidebar" aria-label="<%= t('testimonials.dashboard.nps_responses', default: 'NPS responses') %>">
|
|
37
112
|
<div class="record-list">
|
|
@@ -102,5 +102,17 @@ ar:
|
|
|
102
102
|
promoters: "المروّجون (9–10)"
|
|
103
103
|
passives: "المحايدون (7–8)"
|
|
104
104
|
detractors: "المنتقدون (0–6)"
|
|
105
|
+
nps_band_needs_work: "يحتاج إلى تحسين"
|
|
106
|
+
nps_band_good: "جيد"
|
|
107
|
+
nps_band_great: "جيد جدًا"
|
|
108
|
+
nps_band_excellent: "ممتاز"
|
|
109
|
+
nps_sample_note: "بناءً على %{count} ردًا — عدد قليل جدًا لبناء استنتاج عليه."
|
|
110
|
+
nps_guide: "كيف يُحتسب، وما هي النتيجة الجيدة؟"
|
|
111
|
+
nps_guide_formula: "NPS = نسبة المروّجين ناقص نسبة المنتقدين. المحايدون يُحتسبون ضمن الإجمالي لكن لا يُضافون إلى أي من الطرفين، لذلك يسحبون النتيجة نحو الصفر. تتراوح النتيجة بين −100 و+100، وهي مؤشر وليست نسبة مئوية."
|
|
112
|
+
nps_guide_needs_work: "عدد المنتقدين أكبر من عدد المروّجين."
|
|
113
|
+
nps_guide_good: "النطاق الذي تقع فيه معظم الشركات."
|
|
114
|
+
nps_guide_great: "ولاء قوي."
|
|
115
|
+
nps_guide_excellent: "مستوى عالمي، ونادر."
|
|
116
|
+
nps_guide_caveat: "تختلف المعايير حسب القطاع — البرمجيات غالبًا قرب 40، وشركات الطيران ومزوّدو الإنترنت قرب 0. الاتجاه عبر الوقت يقول أكثر من رقم واحد، والتعليقات تقول أكثر من النتيجة."
|
|
105
117
|
score: "الدرجة"
|
|
106
118
|
comment: "التعليق"
|
|
@@ -102,5 +102,17 @@ bg:
|
|
|
102
102
|
promoters: "Промоутъри (9–10)"
|
|
103
103
|
passives: "Неутрални (7–8)"
|
|
104
104
|
detractors: "Критици (0–6)"
|
|
105
|
+
nps_band_needs_work: "Нужно е подобрение"
|
|
106
|
+
nps_band_good: "Добро"
|
|
107
|
+
nps_band_great: "Много добро"
|
|
108
|
+
nps_band_excellent: "Отлично"
|
|
109
|
+
nps_sample_note: "Въз основа на %{count} отговора — твърде малко за изводи."
|
|
110
|
+
nps_guide: "Как се изчислява и кой резултат е добър?"
|
|
111
|
+
nps_guide_formula: "NPS = % промотори минус % критици. Неутралните влизат в общия брой, но в нито една от двете страни, затова дърпат резултата към нулата. Стойността е от −100 до +100 — това е индекс, а не процент."
|
|
112
|
+
nps_guide_needs_work: "Критиците са повече от промоторите."
|
|
113
|
+
nps_guide_good: "Там, където са повечето компании."
|
|
114
|
+
nps_guide_great: "Силна лоялност."
|
|
115
|
+
nps_guide_excellent: "Световно ниво, среща се рядко."
|
|
116
|
+
nps_guide_caveat: "Ориентирите зависят от бранша — при софтуера често са около 40, при авиокомпаниите и интернет доставчиците — близо до 0. Тенденцията във времето казва повече от едно число, а коментарите — повече от резултата."
|
|
105
117
|
score: "Резултат"
|
|
106
118
|
comment: "Коментар"
|
|
@@ -102,5 +102,17 @@ bn:
|
|
|
102
102
|
promoters: "প্রোমোটার (৯–১০)"
|
|
103
103
|
passives: "নিরপেক্ষ (৭–৮)"
|
|
104
104
|
detractors: "সমালোচক (০–৬)"
|
|
105
|
+
nps_band_needs_work: "উন্নতি দরকার"
|
|
106
|
+
nps_band_good: "ভালো"
|
|
107
|
+
nps_band_great: "খুব ভালো"
|
|
108
|
+
nps_band_excellent: "চমৎকার"
|
|
109
|
+
nps_sample_note: "%{count}টি উত্তরের ভিত্তিতে — সিদ্ধান্তে আসার জন্য যথেষ্ট নয়।"
|
|
110
|
+
nps_guide: "এটি কীভাবে হিসাব হয়, আর কত হলে ভালো?"
|
|
111
|
+
nps_guide_formula: "NPS = প্রোমোটারের শতাংশ বিয়োগ ডিট্র্যাক্টরের শতাংশ। নিরপেক্ষরা মোট সংখ্যায় ধরা হয় কিন্তু কোনো পক্ষেই যায় না, তাই স্কোরকে শূন্যের দিকে টানে। ফল −১০০ থেকে +১০০ পর্যন্ত — এটি স্কোর, শতাংশ নয়।"
|
|
112
|
+
nps_guide_needs_work: "প্রোমোটারের চেয়ে ডিট্র্যাক্টর বেশি।"
|
|
113
|
+
nps_guide_good: "বেশির ভাগ কোম্পানি এখানেই থাকে।"
|
|
114
|
+
nps_guide_great: "শক্ত আনুগত্য।"
|
|
115
|
+
nps_guide_excellent: "বিশ্বমানের, বিরল।"
|
|
116
|
+
nps_guide_caveat: "মানদণ্ড শিল্প অনুযায়ী বদলায় — সফটওয়্যারে প্রায়ই ৪০-এর কাছাকাছি, বিমান সংস্থা ও ইন্টারনেট সরবরাহকারীদের ০-এর কাছাকাছি। একক সংখ্যার চেয়ে সময়ের ধারা বেশি বলে, আর স্কোরের চেয়ে মন্তব্য বেশি বলে।"
|
|
105
117
|
score: "স্কোর"
|
|
106
118
|
comment: "মন্তব্য"
|
|
@@ -102,5 +102,17 @@ de:
|
|
|
102
102
|
promoters: "Promotoren (9–10)"
|
|
103
103
|
passives: "Passive (7–8)"
|
|
104
104
|
detractors: "Detraktoren (0–6)"
|
|
105
|
+
nps_band_needs_work: "Verbesserungsbedarf"
|
|
106
|
+
nps_band_good: "Gut"
|
|
107
|
+
nps_band_great: "Sehr gut"
|
|
108
|
+
nps_band_excellent: "Hervorragend"
|
|
109
|
+
nps_sample_note: "Basiert auf %{count} Antworten — zu wenige für belastbare Schlüsse."
|
|
110
|
+
nps_guide: "Wie wird das berechnet, und was ist ein guter Wert?"
|
|
111
|
+
nps_guide_formula: "NPS = % der Promotoren minus % der Kritiker. Indifferente zählen zur Gesamtzahl, aber zu keiner Seite, und ziehen den Wert damit Richtung null. Das Ergebnis reicht von −100 bis +100 — ein Indexwert, kein Prozentsatz."
|
|
112
|
+
nps_guide_needs_work: "Mehr Kritiker als Promotoren."
|
|
113
|
+
nps_guide_good: "Der Bereich, in dem die meisten Unternehmen liegen."
|
|
114
|
+
nps_guide_great: "Starke Loyalität."
|
|
115
|
+
nps_guide_excellent: "Weltklasse und selten."
|
|
116
|
+
nps_guide_caveat: "Die Richtwerte hängen von der Branche ab — Software liegt oft bei rund 40, Fluggesellschaften und Internetanbieter nahe 0. Der Verlauf über die Zeit sagt mehr als eine einzelne Zahl, und die Kommentare sagen mehr als der Wert."
|
|
105
117
|
score: "Wert"
|
|
106
118
|
comment: "Kommentar"
|
|
@@ -102,5 +102,17 @@ el:
|
|
|
102
102
|
promoters: "Υποστηρικτές (9–10)"
|
|
103
103
|
passives: "Ουδέτεροι (7–8)"
|
|
104
104
|
detractors: "Επικριτές (0–6)"
|
|
105
|
+
nps_band_needs_work: "Χρειάζεται βελτίωση"
|
|
106
|
+
nps_band_good: "Καλό"
|
|
107
|
+
nps_band_great: "Πολύ καλό"
|
|
108
|
+
nps_band_excellent: "Εξαιρετικό"
|
|
109
|
+
nps_sample_note: "Με βάση %{count} απαντήσεις — πολύ λίγες για ασφαλή συμπεράσματα."
|
|
110
|
+
nps_guide: "Πώς υπολογίζεται και ποια βαθμολογία θεωρείται καλή;"
|
|
111
|
+
nps_guide_formula: "NPS = ποσοστό υποστηρικτών μείον ποσοστό επικριτών. Οι ουδέτεροι μετρούν στο σύνολο αλλά σε καμία από τις δύο πλευρές, οπότε τραβούν τη βαθμολογία προς το μηδέν. Το αποτέλεσμα κυμαίνεται από −100 έως +100 — είναι δείκτης, όχι ποσοστό."
|
|
112
|
+
nps_guide_needs_work: "Περισσότεροι επικριτές από υποστηρικτές."
|
|
113
|
+
nps_guide_good: "Εκεί βρίσκονται οι περισσότερες εταιρείες."
|
|
114
|
+
nps_guide_great: "Ισχυρή πιστότητα."
|
|
115
|
+
nps_guide_excellent: "Παγκόσμιου επιπέδου, και σπάνιο."
|
|
116
|
+
nps_guide_caveat: "Τα σημεία αναφοράς αλλάζουν ανά κλάδο — το λογισμικό συχνά κοντά στο 40, οι αεροπορικές και οι πάροχοι ίντερνετ κοντά στο 0. Η τάση στον χρόνο λέει περισσότερα από έναν αριθμό, και τα σχόλια περισσότερα από τη βαθμολογία."
|
|
105
117
|
score: "Βαθμός"
|
|
106
118
|
comment: "Σχόλιο"
|
|
@@ -102,5 +102,17 @@ en:
|
|
|
102
102
|
promoters: "Promoters (9–10)"
|
|
103
103
|
passives: "Passives (7–8)"
|
|
104
104
|
detractors: "Detractors (0–6)"
|
|
105
|
+
nps_band_needs_work: "Needs work"
|
|
106
|
+
nps_band_good: "Good"
|
|
107
|
+
nps_band_great: "Great"
|
|
108
|
+
nps_band_excellent: "Excellent"
|
|
109
|
+
nps_sample_note: "Based on %{count} responses — too few to read much into."
|
|
110
|
+
nps_guide: "How is this calculated, and what is a good score?"
|
|
111
|
+
nps_guide_formula: "NPS = the % of promoters minus the % of detractors. Passives count in the total but on neither side, so they pull the score toward zero. The result runs from −100 to +100 — a score, not a percentage."
|
|
112
|
+
nps_guide_needs_work: "More detractors than promoters."
|
|
113
|
+
nps_guide_good: "Where most companies land."
|
|
114
|
+
nps_guide_great: "Strong loyalty."
|
|
115
|
+
nps_guide_excellent: "World-class, and rare."
|
|
116
|
+
nps_guide_caveat: "Benchmarks move with the industry — software often sits near 40, airlines and internet providers near 0. The trend over time tells you more than any single number, and the comments tell you more than the score."
|
|
105
117
|
score: "Score"
|
|
106
118
|
comment: "Comment"
|
|
@@ -102,5 +102,17 @@ es:
|
|
|
102
102
|
promoters: "Promotores (9–10)"
|
|
103
103
|
passives: "Pasivos (7–8)"
|
|
104
104
|
detractors: "Detractores (0–6)"
|
|
105
|
+
nps_band_needs_work: "Necesita mejorar"
|
|
106
|
+
nps_band_good: "Bueno"
|
|
107
|
+
nps_band_great: "Muy bueno"
|
|
108
|
+
nps_band_excellent: "Excelente"
|
|
109
|
+
nps_sample_note: "Basado en %{count} respuestas: demasiado pocas para sacar conclusiones."
|
|
110
|
+
nps_guide: "¿Cómo se calcula y qué puntuación es buena?"
|
|
111
|
+
nps_guide_formula: "NPS = % de promotores menos % de detractores. Los pasivos cuentan en el total pero en ninguno de los dos lados, así que empujan la puntuación hacia cero. El resultado va de −100 a +100: es un índice, no un porcentaje."
|
|
112
|
+
nps_guide_needs_work: "Más detractores que promotores."
|
|
113
|
+
nps_guide_good: "Donde se sitúa la mayoría de las empresas."
|
|
114
|
+
nps_guide_great: "Lealtad sólida."
|
|
115
|
+
nps_guide_excellent: "De primer nivel, y poco común."
|
|
116
|
+
nps_guide_caveat: "Las referencias cambian según el sector: el software suele rondar 40, y las aerolíneas y los proveedores de internet, cerca de 0. La tendencia en el tiempo dice más que una sola cifra, y los comentarios dicen más que la puntuación."
|
|
105
117
|
score: "Puntuación"
|
|
106
118
|
comment: "Comentario"
|
|
@@ -102,5 +102,17 @@ fr:
|
|
|
102
102
|
promoters: "Promoteurs (9–10)"
|
|
103
103
|
passives: "Passifs (7–8)"
|
|
104
104
|
detractors: "Détracteurs (0–6)"
|
|
105
|
+
nps_band_needs_work: "À améliorer"
|
|
106
|
+
nps_band_good: "Bon"
|
|
107
|
+
nps_band_great: "Très bon"
|
|
108
|
+
nps_band_excellent: "Excellent"
|
|
109
|
+
nps_sample_note: "Basé sur %{count} réponses — trop peu pour en tirer des conclusions."
|
|
110
|
+
nps_guide: "Comment est-il calculé, et qu'est-ce qu'un bon score ?"
|
|
111
|
+
nps_guide_formula: "NPS = % de promoteurs moins % de détracteurs. Les passifs comptent dans le total mais dans aucun des deux camps, ce qui tire le score vers zéro. Le résultat va de −100 à +100 — un score, pas un pourcentage."
|
|
112
|
+
nps_guide_needs_work: "Plus de détracteurs que de promoteurs."
|
|
113
|
+
nps_guide_good: "Là où se situe la plupart des entreprises."
|
|
114
|
+
nps_guide_great: "Forte fidélité."
|
|
115
|
+
nps_guide_excellent: "Niveau mondial, et rare."
|
|
116
|
+
nps_guide_caveat: "Les repères varient selon le secteur — le logiciel tourne souvent autour de 40, les compagnies aériennes et les fournisseurs d'accès près de 0. La tendance dans le temps en dit plus qu'un chiffre isolé, et les commentaires en disent plus que le score."
|
|
105
117
|
score: "Score"
|
|
106
118
|
comment: "Commentaire"
|
|
@@ -102,5 +102,17 @@ hi:
|
|
|
102
102
|
promoters: "प्रमोटर (9–10)"
|
|
103
103
|
passives: "तटस्थ (7–8)"
|
|
104
104
|
detractors: "आलोचक (0–6)"
|
|
105
|
+
nps_band_needs_work: "सुधार चाहिए"
|
|
106
|
+
nps_band_good: "अच्छा"
|
|
107
|
+
nps_band_great: "बहुत अच्छा"
|
|
108
|
+
nps_band_excellent: "उत्कृष्ट"
|
|
109
|
+
nps_sample_note: "%{count} जवाबों पर आधारित — निष्कर्ष निकालने के लिए बहुत कम।"
|
|
110
|
+
nps_guide: "यह कैसे निकलता है, और अच्छा स्कोर क्या है?"
|
|
111
|
+
nps_guide_formula: "NPS = प्रमोटर का प्रतिशत घटा डिट्रैक्टर का प्रतिशत। पैसिव कुल में गिने जाते हैं पर किसी पक्ष में नहीं, इसलिए वे स्कोर को शून्य की ओर खींचते हैं। नतीजा −100 से +100 तक होता है — यह स्कोर है, प्रतिशत नहीं।"
|
|
112
|
+
nps_guide_needs_work: "प्रमोटर से ज़्यादा डिट्रैक्टर हैं।"
|
|
113
|
+
nps_guide_good: "अधिकतर कंपनियाँ यहीं होती हैं।"
|
|
114
|
+
nps_guide_great: "मज़बूत वफ़ादारी।"
|
|
115
|
+
nps_guide_excellent: "विश्वस्तरीय, और दुर्लभ।"
|
|
116
|
+
nps_guide_caveat: "मानक उद्योग के हिसाब से बदलते हैं — सॉफ़्टवेयर अक्सर 40 के आसपास, एयरलाइंस और इंटरनेट प्रदाता 0 के करीब। किसी एक आँकड़े से ज़्यादा समय के साथ रुझान बताता है, और स्कोर से ज़्यादा टिप्पणियाँ।"
|
|
105
117
|
score: "स्कोर"
|
|
106
118
|
comment: "टिप्पणी"
|
|
@@ -102,5 +102,17 @@ hr:
|
|
|
102
102
|
promoters: "Promotori (9–10)"
|
|
103
103
|
passives: "Pasivni (7–8)"
|
|
104
104
|
detractors: "Kritičari (0–6)"
|
|
105
|
+
nps_band_needs_work: "Treba poboljšati"
|
|
106
|
+
nps_band_good: "Dobro"
|
|
107
|
+
nps_band_great: "Vrlo dobro"
|
|
108
|
+
nps_band_excellent: "Izvrsno"
|
|
109
|
+
nps_sample_note: "Na temelju %{count} odgovora — premalo za zaključke."
|
|
110
|
+
nps_guide: "Kako se ovo računa i koji je rezultat dobar?"
|
|
111
|
+
nps_guide_formula: "NPS = postotak promotora minus postotak kritičara. Neutralni se broje u ukupan zbroj, ali ni na jednu stranu, pa vuku rezultat prema nuli. Rezultat je od −100 do +100 — to je indeks, a ne postotak."
|
|
112
|
+
nps_guide_needs_work: "Više kritičara nego promotora."
|
|
113
|
+
nps_guide_good: "Ondje gdje je većina tvrtki."
|
|
114
|
+
nps_guide_great: "Snažna lojalnost."
|
|
115
|
+
nps_guide_excellent: "Svjetska razina, i rijetkost."
|
|
116
|
+
nps_guide_caveat: "Mjerila se razlikuju po industriji — softver je često oko 40, zrakoplovne tvrtke i pružatelji interneta blizu 0. Kretanje kroz vrijeme govori više od jednog broja, a komentari više od rezultata."
|
|
105
117
|
score: "Rezultat"
|
|
106
118
|
comment: "Komentar"
|
|
@@ -102,5 +102,17 @@ id:
|
|
|
102
102
|
promoters: "Promotor (9–10)"
|
|
103
103
|
passives: "Pasif (7–8)"
|
|
104
104
|
detractors: "Detraktor (0–6)"
|
|
105
|
+
nps_band_needs_work: "Perlu perbaikan"
|
|
106
|
+
nps_band_good: "Baik"
|
|
107
|
+
nps_band_great: "Sangat baik"
|
|
108
|
+
nps_band_excellent: "Luar biasa"
|
|
109
|
+
nps_sample_note: "Berdasarkan %{count} tanggapan — terlalu sedikit untuk disimpulkan."
|
|
110
|
+
nps_guide: "Bagaimana ini dihitung, dan berapa yang tergolong bagus?"
|
|
111
|
+
nps_guide_formula: "NPS = persentase promotor dikurangi persentase detraktor. Kelompok pasif dihitung dalam total tetapi tidak masuk ke salah satu sisi, sehingga menarik skor mendekati nol. Hasilnya berkisar dari −100 hingga +100 — sebuah skor, bukan persentase."
|
|
112
|
+
nps_guide_needs_work: "Detraktor lebih banyak daripada promotor."
|
|
113
|
+
nps_guide_good: "Kisaran tempat sebagian besar perusahaan berada."
|
|
114
|
+
nps_guide_great: "Loyalitas yang kuat."
|
|
115
|
+
nps_guide_excellent: "Kelas dunia, dan jarang."
|
|
116
|
+
nps_guide_caveat: "Tolok ukur berbeda di tiap industri — perangkat lunak sering di sekitar 40, maskapai dan penyedia internet mendekati 0. Tren dari waktu ke waktu lebih berarti daripada satu angka, dan komentar lebih berarti daripada skor."
|
|
105
117
|
score: "Skor"
|
|
106
118
|
comment: "Komentar"
|
|
@@ -102,5 +102,17 @@ it:
|
|
|
102
102
|
promoters: "Promotori (9–10)"
|
|
103
103
|
passives: "Passivi (7–8)"
|
|
104
104
|
detractors: "Detrattori (0–6)"
|
|
105
|
+
nps_band_needs_work: "Da migliorare"
|
|
106
|
+
nps_band_good: "Buono"
|
|
107
|
+
nps_band_great: "Ottimo"
|
|
108
|
+
nps_band_excellent: "Eccellente"
|
|
109
|
+
nps_sample_note: "Basato su %{count} risposte: troppo poche per trarne conclusioni."
|
|
110
|
+
nps_guide: "Come si calcola e qual è un buon punteggio?"
|
|
111
|
+
nps_guide_formula: "NPS = % di promotori meno % di detrattori. I passivi contano nel totale ma in nessuna delle due parti, quindi spingono il punteggio verso lo zero. Il risultato va da −100 a +100: è un indice, non una percentuale."
|
|
112
|
+
nps_guide_needs_work: "Più detrattori che promotori."
|
|
113
|
+
nps_guide_good: "Dove si colloca la maggior parte delle aziende."
|
|
114
|
+
nps_guide_great: "Forte fedeltà."
|
|
115
|
+
nps_guide_excellent: "Livello mondiale, e raro."
|
|
116
|
+
nps_guide_caveat: "I valori di riferimento cambiano con il settore: il software si attesta spesso intorno a 40, le compagnie aeree e i provider internet vicino a 0. L'andamento nel tempo dice più di un singolo numero, e i commenti dicono più del punteggio."
|
|
105
117
|
score: "Punteggio"
|
|
106
118
|
comment: "Commento"
|
|
@@ -102,5 +102,17 @@ ja:
|
|
|
102
102
|
promoters: "推奨者(9–10)"
|
|
103
103
|
passives: "中立者(7–8)"
|
|
104
104
|
detractors: "批判者(0–6)"
|
|
105
|
+
nps_band_needs_work: "要改善"
|
|
106
|
+
nps_band_good: "良好"
|
|
107
|
+
nps_band_great: "優秀"
|
|
108
|
+
nps_band_excellent: "卓越"
|
|
109
|
+
nps_sample_note: "%{count}件の回答に基づく結果です。判断するにはまだ少なすぎます。"
|
|
110
|
+
nps_guide: "どう計算され、どのくらいなら良いのか"
|
|
111
|
+
nps_guide_formula: "NPS = 推奨者の割合 − 批判者の割合。中立者は母数には入りますがどちらにも数えないため、スコアをゼロに近づけます。結果は −100 から +100 までで、パーセントではなくスコアです。"
|
|
112
|
+
nps_guide_needs_work: "推奨者より批判者が多い状態です。"
|
|
113
|
+
nps_guide_good: "多くの企業がこの範囲に入ります。"
|
|
114
|
+
nps_guide_great: "強いロイヤルティがあります。"
|
|
115
|
+
nps_guide_excellent: "世界水準で、めったにない値です。"
|
|
116
|
+
nps_guide_caveat: "目安は業界によって変わります。ソフトウェアは 40 前後、航空会社やインターネット回線事業者は 0 付近になりがちです。単独の数値より時間の推移が、スコアよりコメントのほうが多くを語ります。"
|
|
105
117
|
score: "スコア"
|
|
106
118
|
comment: "コメント"
|
|
@@ -102,5 +102,17 @@ ko:
|
|
|
102
102
|
promoters: "추천 고객 (9–10)"
|
|
103
103
|
passives: "중립 고객 (7–8)"
|
|
104
104
|
detractors: "비추천 고객 (0–6)"
|
|
105
|
+
nps_band_needs_work: "개선 필요"
|
|
106
|
+
nps_band_good: "좋음"
|
|
107
|
+
nps_band_great: "매우 좋음"
|
|
108
|
+
nps_band_excellent: "탁월함"
|
|
109
|
+
nps_sample_note: "응답 %{count}건 기준 — 판단하기에는 너무 적습니다."
|
|
110
|
+
nps_guide: "어떻게 계산되며, 어느 정도면 좋은 점수인가요?"
|
|
111
|
+
nps_guide_formula: "NPS = 추천 고객 비율 − 비추천 고객 비율. 중립 고객은 전체에는 포함되지만 어느 쪽에도 들어가지 않아 점수를 0에 가깝게 만듭니다. 결과는 −100에서 +100 사이이며, 백분율이 아니라 점수입니다."
|
|
112
|
+
nps_guide_needs_work: "추천 고객보다 비추천 고객이 많습니다."
|
|
113
|
+
nps_guide_good: "대부분의 기업이 속하는 구간입니다."
|
|
114
|
+
nps_guide_great: "충성도가 높습니다."
|
|
115
|
+
nps_guide_excellent: "세계적인 수준으로, 보기 드뭅니다."
|
|
116
|
+
nps_guide_caveat: "기준은 업종에 따라 달라집니다. 소프트웨어는 보통 40 안팎, 항공사와 인터넷 사업자는 0 부근입니다. 하나의 숫자보다 시간에 따른 추이가, 점수보다 코멘트가 더 많은 것을 알려 줍니다."
|
|
105
117
|
score: "점수"
|
|
106
118
|
comment: "의견"
|
|
@@ -102,5 +102,17 @@ lb:
|
|
|
102
102
|
promoters: "Promoteuren (9–10)"
|
|
103
103
|
passives: "Passiv (7–8)"
|
|
104
104
|
detractors: "Kritiker (0–6)"
|
|
105
|
+
nps_band_needs_work: "Muss besser ginn"
|
|
106
|
+
nps_band_good: "Gutt"
|
|
107
|
+
nps_band_great: "Ganz gutt"
|
|
108
|
+
nps_band_excellent: "Excellent"
|
|
109
|
+
nps_sample_note: "Baséiert op %{count} Äntwerten — ze wéineg fir doraus eppes ofzeleeden."
|
|
110
|
+
nps_guide: "Wéi gëtt dat gerechent, a wat ass e gudde Score?"
|
|
111
|
+
nps_guide_formula: "NPS = % vun de Promoteuren ofzunn % vun de Kritiker. D'Passiv zielen an der Gesamtzuel mat, awer op kenger vun de Säiten, sou datt se de Score Richtung null zéien. D'Resultat geet vu −100 bis +100 — e Score, kee Prozentsaz."
|
|
112
|
+
nps_guide_needs_work: "Méi Kritiker wéi Promoteuren."
|
|
113
|
+
nps_guide_good: "Do, wou déi meescht Betriber leien."
|
|
114
|
+
nps_guide_great: "Staark Trei."
|
|
115
|
+
nps_guide_excellent: "Weltklass, a seelen."
|
|
116
|
+
nps_guide_caveat: "D'Richtwäerter hänke vum Secteur of — Software läit dacks ronn 40, Fluchgesellschaften an Internetfournisseuren no bei 0. D'Entwécklung iwwer d'Zäit seet méi wéi eng eenzel Zuel, an d'Kommentaren soe méi wéi de Score."
|
|
105
117
|
score: "Score"
|
|
106
118
|
comment: "Kommentar"
|
|
@@ -102,5 +102,17 @@ nl:
|
|
|
102
102
|
promoters: "Promotors (9–10)"
|
|
103
103
|
passives: "Passieven (7–8)"
|
|
104
104
|
detractors: "Criticasters (0–6)"
|
|
105
|
+
nps_band_needs_work: "Moet beter"
|
|
106
|
+
nps_band_good: "Goed"
|
|
107
|
+
nps_band_great: "Zeer goed"
|
|
108
|
+
nps_band_excellent: "Uitstekend"
|
|
109
|
+
nps_sample_note: "Gebaseerd op %{count} reacties — te weinig om conclusies aan te verbinden."
|
|
110
|
+
nps_guide: "Hoe wordt dit berekend en wat is een goede score?"
|
|
111
|
+
nps_guide_formula: "NPS = % promotors min % criticasters. Passieven tellen mee in het totaal maar aan geen van beide kanten, waardoor ze de score richting nul trekken. De uitkomst loopt van −100 tot +100 — een score, geen percentage."
|
|
112
|
+
nps_guide_needs_work: "Meer criticasters dan promotors."
|
|
113
|
+
nps_guide_good: "Waar de meeste bedrijven zitten."
|
|
114
|
+
nps_guide_great: "Sterke loyaliteit."
|
|
115
|
+
nps_guide_excellent: "Wereldklasse, en zeldzaam."
|
|
116
|
+
nps_guide_caveat: "Richtwaarden verschillen per branche — software zit vaak rond de 40, luchtvaartmaatschappijen en internetproviders dicht bij 0. De trend over tijd zegt meer dan één getal, en de opmerkingen zeggen meer dan de score."
|
|
105
117
|
score: "Score"
|
|
106
118
|
comment: "Opmerking"
|
|
@@ -102,5 +102,17 @@ pl:
|
|
|
102
102
|
promoters: "Promotorzy (9–10)"
|
|
103
103
|
passives: "Neutralni (7–8)"
|
|
104
104
|
detractors: "Krytycy (0–6)"
|
|
105
|
+
nps_band_needs_work: "Wymaga pracy"
|
|
106
|
+
nps_band_good: "Dobrze"
|
|
107
|
+
nps_band_great: "Bardzo dobrze"
|
|
108
|
+
nps_band_excellent: "Znakomicie"
|
|
109
|
+
nps_sample_note: "Na podstawie %{count} odpowiedzi — zbyt mało, by wyciągać wnioski."
|
|
110
|
+
nps_guide: "Jak to jest liczone i jaki wynik jest dobry?"
|
|
111
|
+
nps_guide_formula: "NPS = % promotorów minus % krytyków. Neutralni liczą się do sumy, ale do żadnej ze stron, więc ciągną wynik w stronę zera. Wartość mieści się w przedziale od −100 do +100 — to wskaźnik, nie procent."
|
|
112
|
+
nps_guide_needs_work: "Więcej krytyków niż promotorów."
|
|
113
|
+
nps_guide_good: "Poziom większości firm."
|
|
114
|
+
nps_guide_great: "Silna lojalność."
|
|
115
|
+
nps_guide_excellent: "Poziom światowy, spotykany rzadko."
|
|
116
|
+
nps_guide_caveat: "Punkty odniesienia zależą od branży — oprogramowanie często ma około 40, linie lotnicze i dostawcy internetu blisko 0. Trend w czasie mówi więcej niż pojedyncza liczba, a komentarze więcej niż sam wynik."
|
|
105
117
|
score: "Wynik"
|
|
106
118
|
comment: "Komentarz"
|
|
@@ -102,5 +102,17 @@ pt:
|
|
|
102
102
|
promoters: "Promotores (9–10)"
|
|
103
103
|
passives: "Neutros (7–8)"
|
|
104
104
|
detractors: "Detratores (0–6)"
|
|
105
|
+
nps_band_needs_work: "Precisa melhorar"
|
|
106
|
+
nps_band_good: "Bom"
|
|
107
|
+
nps_band_great: "Muito bom"
|
|
108
|
+
nps_band_excellent: "Excelente"
|
|
109
|
+
nps_sample_note: "Com base em %{count} respostas — poucas demais para tirar conclusões."
|
|
110
|
+
nps_guide: "Como isto é calculado e o que é uma boa pontuação?"
|
|
111
|
+
nps_guide_formula: "NPS = % de promotores menos % de detratores. Os neutros contam no total, mas não em nenhum dos lados, por isso puxam a pontuação para zero. O resultado vai de −100 a +100 — é um índice, não uma percentagem."
|
|
112
|
+
nps_guide_needs_work: "Mais detratores do que promotores."
|
|
113
|
+
nps_guide_good: "Onde fica a maioria das empresas."
|
|
114
|
+
nps_guide_great: "Lealdade forte."
|
|
115
|
+
nps_guide_excellent: "Nível mundial, e raro."
|
|
116
|
+
nps_guide_caveat: "As referências variam por setor — software costuma ficar perto de 40, companhias aéreas e provedores de internet perto de 0. A tendência ao longo do tempo diz mais do que um número isolado, e os comentários dizem mais do que a pontuação."
|
|
105
117
|
score: "Nota"
|
|
106
118
|
comment: "Comentário"
|
|
@@ -102,5 +102,17 @@ ro:
|
|
|
102
102
|
promoters: "Promotori (9–10)"
|
|
103
103
|
passives: "Pasivi (7–8)"
|
|
104
104
|
detractors: "Detractori (0–6)"
|
|
105
|
+
nps_band_needs_work: "Necesită îmbunătățiri"
|
|
106
|
+
nps_band_good: "Bun"
|
|
107
|
+
nps_band_great: "Foarte bun"
|
|
108
|
+
nps_band_excellent: "Excelent"
|
|
109
|
+
nps_sample_note: "Pe baza a %{count} răspunsuri — prea puține pentru concluzii."
|
|
110
|
+
nps_guide: "Cum se calculează și ce înseamnă un scor bun?"
|
|
111
|
+
nps_guide_formula: "NPS = procentul de promotori minus procentul de detractori. Pasivii intră în total, dar în niciuna dintre părți, așa că trag scorul spre zero. Rezultatul merge de la −100 la +100 — este un scor, nu un procent."
|
|
112
|
+
nps_guide_needs_work: "Mai mulți detractori decât promotori."
|
|
113
|
+
nps_guide_good: "Acolo unde se află majoritatea companiilor."
|
|
114
|
+
nps_guide_great: "Loialitate puternică."
|
|
115
|
+
nps_guide_excellent: "Nivel mondial, și rar."
|
|
116
|
+
nps_guide_caveat: "Reperele diferă de la o industrie la alta — software-ul stă adesea în jur de 40, companiile aeriene și furnizorii de internet aproape de 0. Tendința în timp spune mai mult decât un singur număr, iar comentariile spun mai mult decât scorul."
|
|
105
117
|
score: "Scor"
|
|
106
118
|
comment: "Comentariu"
|
|
@@ -102,5 +102,17 @@ ru:
|
|
|
102
102
|
promoters: "Промоутеры (9–10)"
|
|
103
103
|
passives: "Нейтралы (7–8)"
|
|
104
104
|
detractors: "Критики (0–6)"
|
|
105
|
+
nps_band_needs_work: "Требует работы"
|
|
106
|
+
nps_band_good: "Хорошо"
|
|
107
|
+
nps_band_great: "Отлично"
|
|
108
|
+
nps_band_excellent: "Превосходно"
|
|
109
|
+
nps_sample_note: "Основано на %{count} ответах — слишком мало, чтобы делать выводы."
|
|
110
|
+
nps_guide: "Как это считается и какой результат хороший?"
|
|
111
|
+
nps_guide_formula: "NPS = % промоутеров минус % критиков. Нейтралы входят в общее число, но не идут ни в одну из сторон, поэтому тянут результат к нулю. Значение лежит в диапазоне от −100 до +100 — это индекс, а не проценты."
|
|
112
|
+
nps_guide_needs_work: "Критиков больше, чем промоутеров."
|
|
113
|
+
nps_guide_good: "Уровень большинства компаний."
|
|
114
|
+
nps_guide_great: "Высокая лояльность."
|
|
115
|
+
nps_guide_excellent: "Мировой уровень, встречается редко."
|
|
116
|
+
nps_guide_caveat: "Ориентиры зависят от отрасли: у софта это часто около 40, у авиакомпаний и провайдеров — около 0. Динамика во времени говорит больше, чем одно число, а комментарии — больше, чем сам индекс."
|
|
105
117
|
score: "Оценка"
|
|
106
118
|
comment: "Комментарий"
|
|
@@ -102,5 +102,17 @@ th:
|
|
|
102
102
|
promoters: "ผู้สนับสนุน (9–10)"
|
|
103
103
|
passives: "เป็นกลาง (7–8)"
|
|
104
104
|
detractors: "ผู้ไม่พอใจ (0–6)"
|
|
105
|
+
nps_band_needs_work: "ต้องปรับปรุง"
|
|
106
|
+
nps_band_good: "ดี"
|
|
107
|
+
nps_band_great: "ดีมาก"
|
|
108
|
+
nps_band_excellent: "ยอดเยี่ยม"
|
|
109
|
+
nps_sample_note: "จากคำตอบ %{count} รายการ — น้อยเกินกว่าจะสรุปอะไรได้"
|
|
110
|
+
nps_guide: "คำนวณอย่างไร และเท่าไรถึงเรียกว่าดี"
|
|
111
|
+
nps_guide_formula: "NPS = สัดส่วนผู้สนับสนุน ลบ สัดส่วนผู้ไม่พอใจ กลุ่มเฉย ๆ นับรวมในยอดทั้งหมดแต่ไม่นับเข้าฝั่งใด จึงดึงคะแนนเข้าใกล้ศูนย์ ผลลัพธ์อยู่ระหว่าง −100 ถึง +100 เป็นคะแนน ไม่ใช่เปอร์เซ็นต์"
|
|
112
|
+
nps_guide_needs_work: "มีผู้ไม่พอใจมากกว่าผู้สนับสนุน"
|
|
113
|
+
nps_guide_good: "ช่วงที่บริษัทส่วนใหญ่อยู่"
|
|
114
|
+
nps_guide_great: "ความภักดีสูง"
|
|
115
|
+
nps_guide_excellent: "ระดับโลก และพบได้ยาก"
|
|
116
|
+
nps_guide_caveat: "เกณฑ์เปรียบเทียบต่างกันไปตามอุตสาหกรรม ซอฟต์แวร์มักอยู่ราว 40 ส่วนสายการบินและผู้ให้บริการอินเทอร์เน็ตใกล้ 0 แนวโน้มตามเวลาบอกอะไรได้มากกว่าตัวเลขเดียว และความคิดเห็นบอกได้มากกว่าคะแนน"
|
|
105
117
|
score: "คะแนน"
|
|
106
118
|
comment: "ความคิดเห็น"
|
|
@@ -102,5 +102,17 @@ tr:
|
|
|
102
102
|
promoters: "Destekleyenler (9–10)"
|
|
103
103
|
passives: "Pasifler (7–8)"
|
|
104
104
|
detractors: "Eleştirenler (0–6)"
|
|
105
|
+
nps_band_needs_work: "Geliştirilmeli"
|
|
106
|
+
nps_band_good: "İyi"
|
|
107
|
+
nps_band_great: "Çok iyi"
|
|
108
|
+
nps_band_excellent: "Mükemmel"
|
|
109
|
+
nps_sample_note: "%{count} yanıta dayanıyor — sonuç çıkarmak için fazla az."
|
|
110
|
+
nps_guide: "Bu nasıl hesaplanır ve iyi bir puan nedir?"
|
|
111
|
+
nps_guide_formula: "NPS = tavsiye edenlerin yüzdesi eksi eleştirenlerin yüzdesi. Kararsızlar toplama dahildir ama iki taraftan birine yazılmaz, bu yüzden puanı sıfıra doğru çeker. Sonuç −100 ile +100 arasındadır — yüzde değil, bir puandır."
|
|
112
|
+
nps_guide_needs_work: "Eleştirenler tavsiye edenlerden fazla."
|
|
113
|
+
nps_guide_good: "Çoğu şirketin bulunduğu aralık."
|
|
114
|
+
nps_guide_great: "Güçlü bağlılık."
|
|
115
|
+
nps_guide_excellent: "Dünya standartlarında ve nadir."
|
|
116
|
+
nps_guide_caveat: "Ölçütler sektöre göre değişir — yazılımda genelde 40 civarı, havayollarında ve internet sağlayıcılarında 0'a yakındır. Zaman içindeki eğilim tek bir sayıdan, yorumlar ise puandan daha çok şey anlatır."
|
|
105
117
|
score: "Puan"
|
|
106
118
|
comment: "Yorum"
|
|
@@ -102,5 +102,17 @@ uk:
|
|
|
102
102
|
promoters: "Промоутери (9–10)"
|
|
103
103
|
passives: "Нейтральні (7–8)"
|
|
104
104
|
detractors: "Критики (0–6)"
|
|
105
|
+
nps_band_needs_work: "Потребує роботи"
|
|
106
|
+
nps_band_good: "Добре"
|
|
107
|
+
nps_band_great: "Чудово"
|
|
108
|
+
nps_band_excellent: "Відмінно"
|
|
109
|
+
nps_sample_note: "На основі %{count} відповідей — замало, щоб робити висновки."
|
|
110
|
+
nps_guide: "Як це обчислюється і який результат вважається добрим?"
|
|
111
|
+
nps_guide_formula: "NPS = % промоутерів мінус % критиків. Нейтральні входять до загальної кількості, але не до жодної зі сторін, тому тягнуть результат до нуля. Значення — від −100 до +100 — це індекс, а не відсотки."
|
|
112
|
+
nps_guide_needs_work: "Критиків більше, ніж промоутерів."
|
|
113
|
+
nps_guide_good: "Рівень більшості компаній."
|
|
114
|
+
nps_guide_great: "Висока лояльність."
|
|
115
|
+
nps_guide_excellent: "Світовий рівень, трапляється рідко."
|
|
116
|
+
nps_guide_caveat: "Орієнтири залежать від галузі: у програмного забезпечення це часто близько 40, у авіакомпаній та провайдерів — близько 0. Динаміка в часі говорить більше, ніж одне число, а коментарі — більше, ніж сам індекс."
|
|
105
117
|
score: "Оцінка"
|
|
106
118
|
comment: "Коментар"
|
|
@@ -102,5 +102,17 @@ ur:
|
|
|
102
102
|
promoters: "حامی (9–10)"
|
|
103
103
|
passives: "غیر جانبدار (7–8)"
|
|
104
104
|
detractors: "ناقدین (0–6)"
|
|
105
|
+
nps_band_needs_work: "بہتری درکار"
|
|
106
|
+
nps_band_good: "اچھا"
|
|
107
|
+
nps_band_great: "بہت اچھا"
|
|
108
|
+
nps_band_excellent: "شاندار"
|
|
109
|
+
nps_sample_note: "%{count} جوابات کی بنیاد پر — نتیجہ نکالنے کے لیے بہت کم۔"
|
|
110
|
+
nps_guide: "یہ کیسے نکلتا ہے، اور اچھا اسکور کیا ہے؟"
|
|
111
|
+
nps_guide_formula: "NPS = پروموٹرز کا فیصد منفی ناقدین کا فیصد۔ غیر جانبدار کل تعداد میں شمار ہوتے ہیں مگر کسی طرف نہیں، اس لیے وہ اسکور کو صفر کی طرف کھینچتے ہیں۔ نتیجہ −100 سے +100 تک ہوتا ہے — یہ اسکور ہے، فیصد نہیں۔"
|
|
112
|
+
nps_guide_needs_work: "پروموٹرز سے زیادہ ناقدین ہیں۔"
|
|
113
|
+
nps_guide_good: "زیادہ تر کمپنیاں یہیں ہوتی ہیں۔"
|
|
114
|
+
nps_guide_great: "مضبوط وفاداری۔"
|
|
115
|
+
nps_guide_excellent: "عالمی معیار، اور کم یاب۔"
|
|
116
|
+
nps_guide_caveat: "معیار صنعت کے ساتھ بدلتے ہیں — سافٹ ویئر اکثر 40 کے آس پاس، ایئر لائنز اور انٹرنیٹ فراہم کنندگان 0 کے قریب۔ کسی ایک عدد سے زیادہ وقت کے ساتھ رجحان بتاتا ہے، اور اسکور سے زیادہ تبصرے۔"
|
|
105
117
|
score: "اسکور"
|
|
106
118
|
comment: "تبصرہ"
|
|
@@ -102,5 +102,17 @@ vi:
|
|
|
102
102
|
promoters: "Người ủng hộ (9–10)"
|
|
103
103
|
passives: "Trung lập (7–8)"
|
|
104
104
|
detractors: "Người phản đối (0–6)"
|
|
105
|
+
nps_band_needs_work: "Cần cải thiện"
|
|
106
|
+
nps_band_good: "Tốt"
|
|
107
|
+
nps_band_great: "Rất tốt"
|
|
108
|
+
nps_band_excellent: "Xuất sắc"
|
|
109
|
+
nps_sample_note: "Dựa trên %{count} phản hồi — quá ít để rút ra kết luận."
|
|
110
|
+
nps_guide: "Chỉ số này tính thế nào, và bao nhiêu là tốt?"
|
|
111
|
+
nps_guide_formula: "NPS = % người ủng hộ trừ % người phản đối. Nhóm trung lập được tính vào tổng nhưng không thuộc bên nào, nên kéo điểm về gần 0. Kết quả nằm trong khoảng −100 đến +100 — là điểm số, không phải phần trăm."
|
|
112
|
+
nps_guide_needs_work: "Người phản đối nhiều hơn người ủng hộ."
|
|
113
|
+
nps_guide_good: "Mức mà phần lớn công ty đạt được."
|
|
114
|
+
nps_guide_great: "Mức độ trung thành cao."
|
|
115
|
+
nps_guide_excellent: "Đẳng cấp thế giới, và hiếm gặp."
|
|
116
|
+
nps_guide_caveat: "Mốc tham chiếu thay đổi theo ngành — phần mềm thường quanh 40, hàng không và nhà mạng gần 0. Xu hướng theo thời gian nói lên nhiều hơn một con số, và các bình luận nói lên nhiều hơn điểm số."
|
|
105
117
|
score: "Điểm"
|
|
106
118
|
comment: "Bình luận"
|
|
@@ -102,5 +102,17 @@ zh-CN:
|
|
|
102
102
|
promoters: "推荐者(9–10)"
|
|
103
103
|
passives: "中立者(7–8)"
|
|
104
104
|
detractors: "贬损者(0–6)"
|
|
105
|
+
nps_band_needs_work: "有待改进"
|
|
106
|
+
nps_band_good: "良好"
|
|
107
|
+
nps_band_great: "优秀"
|
|
108
|
+
nps_band_excellent: "卓越"
|
|
109
|
+
nps_sample_note: "基于 %{count} 条回复,样本太少,参考价值有限。"
|
|
110
|
+
nps_guide: "这个分数怎么算,多少算好?"
|
|
111
|
+
nps_guide_formula: "NPS = 推荐者占比 − 贬损者占比。被动者计入总数,但不计入任何一方,因此会把分数拉向零。结果范围是 −100 到 +100,是分数而不是百分比。"
|
|
112
|
+
nps_guide_needs_work: "贬损者多于推荐者。"
|
|
113
|
+
nps_guide_good: "多数公司所处的区间。"
|
|
114
|
+
nps_guide_great: "客户忠诚度很高。"
|
|
115
|
+
nps_guide_excellent: "世界一流水平,相当少见。"
|
|
116
|
+
nps_guide_caveat: "基准随行业而变:软件行业常在 40 左右,航空公司和网络运营商接近 0。趋势比单个数字更有意义,评论又比分数更有意义。"
|
|
105
117
|
score: "分数"
|
|
106
118
|
comment: "评论"
|
|
@@ -12,8 +12,15 @@ module Testimonials
|
|
|
12
12
|
|
|
13
13
|
desc 'Installs testimonials: config initializer, migration, and engine mount.'
|
|
14
14
|
|
|
15
|
+
# NPS is the one part of the gem an app can genuinely not want. Skipping
|
|
16
|
+
# it leaves out the table and writes `config.nps = false`, so nothing
|
|
17
|
+
# ever reaches a table that isn't there — no runtime introspection, no
|
|
18
|
+
# boot-time database call. `testimonials:nps` adds it later.
|
|
19
|
+
class_option :skip_nps, type: :boolean, default: false,
|
|
20
|
+
desc: 'Leave out the NPS table and turn the NPS flow off'
|
|
21
|
+
|
|
15
22
|
def create_initializer
|
|
16
|
-
|
|
23
|
+
template 'initializer.rb.tt', 'config/initializers/testimonials.rb'
|
|
17
24
|
end
|
|
18
25
|
|
|
19
26
|
def create_migration_file
|
|
@@ -29,8 +36,12 @@ module Testimonials
|
|
|
29
36
|
say "\ntestimonials installed. Run `rails db:migrate`, then add", :green
|
|
30
37
|
say '`<%= testimonials_tag %>` before </body> in your layout.'
|
|
31
38
|
say 'Triage testimonials at /testimonials (development only until you set config.authorize_admin).'
|
|
32
|
-
|
|
39
|
+
samples = options[:skip_nps] ? 'testimonials' : 'testimonials and NPS'
|
|
40
|
+
say "Optional: run `bin/rails testimonials:seed_demo` for sample #{samples}."
|
|
33
41
|
say "Collect from outside the app via /testimonials/new.\n"
|
|
42
|
+
return unless options[:skip_nps]
|
|
43
|
+
|
|
44
|
+
say 'Installed without NPS. Add it later with `bin/rails generate testimonials:nps`.', :yellow
|
|
34
45
|
end
|
|
35
46
|
|
|
36
47
|
private
|
|
@@ -27,6 +27,7 @@ class CreateTestimonialsTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
27
27
|
add_index :testimonials_testimonials, %i[tenant status]
|
|
28
28
|
add_index :testimonials_testimonials, %i[tenant status consent_given]
|
|
29
29
|
|
|
30
|
+
<% unless options[:skip_nps] -%>
|
|
30
31
|
create_table :testimonials_nps_responses do |t|
|
|
31
32
|
t.integer :score, null: false
|
|
32
33
|
t.text :comment
|
|
@@ -42,6 +43,7 @@ class CreateTestimonialsTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
42
43
|
end
|
|
43
44
|
add_index :testimonials_nps_responses, %i[tenant score]
|
|
44
45
|
|
|
46
|
+
<% end -%>
|
|
45
47
|
create_table :testimonials_prompt_events do |t|
|
|
46
48
|
t.string :kind, null: false
|
|
47
49
|
t.string :action, null: false
|
|
@@ -84,7 +84,15 @@ Testimonials.configure do |config|
|
|
|
84
84
|
# config.public_api = false
|
|
85
85
|
|
|
86
86
|
# NPS surveys. Promoters (9–10) are offered the testimonial form right away.
|
|
87
|
+
<% if options[:skip_nps] -%>
|
|
88
|
+
# Installed with --skip-nps, so there is no testimonials_nps_responses table
|
|
89
|
+
# and the NPS flow, dashboard tab and public page stay off. To add it later:
|
|
90
|
+
# `bin/rails generate testimonials:nps && bin/rails db:migrate`, then flip
|
|
91
|
+
# this to true.
|
|
92
|
+
config.nps = false
|
|
93
|
+
<% else -%>
|
|
87
94
|
# config.nps = true
|
|
95
|
+
<% end -%>
|
|
88
96
|
# config.nps_reprompt_after = 90.days
|
|
89
97
|
|
|
90
98
|
# Called with each saved Testimonial or NpsResponse — notify Slack, email…
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
|
|
6
|
+
module Testimonials
|
|
7
|
+
module Generators
|
|
8
|
+
# For apps that installed with --skip-nps and later want the 0–10 survey:
|
|
9
|
+
# creates the testimonials_nps_responses table. A full install already has
|
|
10
|
+
# it. Additive and safe — nothing reads the table until config.nps is true.
|
|
11
|
+
#
|
|
12
|
+
# bin/rails generate testimonials:nps && bin/rails db:migrate
|
|
13
|
+
class NpsGenerator < Rails::Generators::Base
|
|
14
|
+
include ActiveRecord::Generators::Migration
|
|
15
|
+
|
|
16
|
+
source_root File.expand_path('templates', __dir__)
|
|
17
|
+
|
|
18
|
+
desc 'Adds the NPS responses table for an install that skipped it.'
|
|
19
|
+
|
|
20
|
+
def create_migration_file
|
|
21
|
+
migration_template 'create_testimonials_nps_responses.rb.tt',
|
|
22
|
+
'db/migrate/create_testimonials_nps_responses.rb'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def post_install
|
|
26
|
+
say "\nNPS table queued. Run `rails db:migrate`, then set", :green
|
|
27
|
+
say 'config.nps = true in config/initializers/testimonials.rb to turn the flow on.'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def migration_version
|
|
33
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateTestimonialsNpsResponses < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :testimonials_nps_responses do |t|
|
|
6
|
+
t.integer :score, null: false
|
|
7
|
+
t.text :comment
|
|
8
|
+
t.string :author_id
|
|
9
|
+
t.string :name
|
|
10
|
+
t.string :email
|
|
11
|
+
t.string :page_url
|
|
12
|
+
t.string :user_agent
|
|
13
|
+
t.string :locale
|
|
14
|
+
t.string :tenant # opaque per-tenant key; nil = single global collection
|
|
15
|
+
|
|
16
|
+
t.timestamps
|
|
17
|
+
end
|
|
18
|
+
add_index :testimonials_nps_responses, %i[tenant score]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
class AddTenantToTestimonials < ActiveRecord::Migration<%= migration_version %>
|
|
4
4
|
def change
|
|
5
5
|
add_column :testimonials_testimonials, :tenant, :string
|
|
6
|
-
add_column :testimonials_nps_responses, :tenant, :string
|
|
7
6
|
add_column :testimonials_prompt_events, :tenant, :string
|
|
8
7
|
|
|
9
8
|
add_index :testimonials_testimonials, %i[tenant status]
|
|
10
9
|
add_index :testimonials_testimonials, %i[tenant status consent_given]
|
|
11
|
-
add_index :testimonials_nps_responses, %i[tenant score]
|
|
12
10
|
add_index :testimonials_prompt_events, %i[tenant author_id kind]
|
|
13
11
|
add_index :testimonials_prompt_events, %i[tenant visitor_token kind]
|
|
12
|
+
|
|
13
|
+
# An install run with --skip-nps has no NPS table to add the column to.
|
|
14
|
+
return unless table_exists?(:testimonials_nps_responses)
|
|
15
|
+
|
|
16
|
+
add_column :testimonials_nps_responses, :tenant, :string
|
|
17
|
+
add_index :testimonials_nps_responses, %i[tenant score]
|
|
14
18
|
end
|
|
15
19
|
end
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
--pending: #d97706; --approved: #16a34a; --archived: #6b7280;
|
|
6
6
|
--star: #f59e0b; --danger: #dc2626;
|
|
7
7
|
--promoter: #16a34a; --passive: #d97706; --detractor: #dc2626;
|
|
8
|
+
/* The four bands an NPS number falls into: bad, ordinary, strong, rare. */
|
|
9
|
+
--nps-needs-work: #dc2626; --nps-good: #d97706; --nps-great: #16a34a; --nps-excellent: #0f766e;
|
|
8
10
|
}
|
|
9
11
|
@media (prefers-color-scheme: dark) {
|
|
10
12
|
:root {
|
|
@@ -86,6 +88,51 @@ img.avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; }
|
|
|
86
88
|
border-radius: 12px; padding: 14px 16px; }
|
|
87
89
|
.stat b { display: block; font-size: 26px; }
|
|
88
90
|
.stat span { color: var(--muted); font-size: 13px; }
|
|
91
|
+
|
|
92
|
+
/* NPS score card: the number, the band it falls in, and where it sits on the
|
|
93
|
+
−100..100 scale — so nobody has to google whether 50 is any good. */
|
|
94
|
+
.stat-nps { flex: 2.4; min-width: 240px; }
|
|
95
|
+
.stat-headline { display: flex; align-items: center; gap: 10px; }
|
|
96
|
+
.stat-headline b { display: inline-block; }
|
|
97
|
+
/* `.stat span` would otherwise mute the badge's own white text. */
|
|
98
|
+
.stat .badge { color: #fff; font-size: 12px; }
|
|
99
|
+
.badge.nps-band-needs-work { background: var(--nps-needs-work); }
|
|
100
|
+
.badge.nps-band-good { background: var(--nps-good); }
|
|
101
|
+
.badge.nps-band-great { background: var(--nps-great); }
|
|
102
|
+
.badge.nps-band-excellent { background: var(--nps-excellent); }
|
|
103
|
+
.nps-meter { direction: ltr; margin-top: 10px; }
|
|
104
|
+
.nps-meter-track { position: relative; display: flex; height: 6px; border-radius: 999px; overflow: hidden; }
|
|
105
|
+
.nps-seg { display: block; height: 100%; opacity: .45; }
|
|
106
|
+
.nps-seg.nps-band-needs-work { flex: 100; background: var(--nps-needs-work); }
|
|
107
|
+
.nps-seg.nps-band-good { flex: 30; background: var(--nps-good); }
|
|
108
|
+
.nps-seg.nps-band-great { flex: 40; background: var(--nps-great); }
|
|
109
|
+
.nps-seg.nps-band-excellent { flex: 30; background: var(--nps-excellent); }
|
|
110
|
+
/* Hidden until dashboard.js reads data-nps-marker and positions it, so a
|
|
111
|
+
score of 50 never renders as if it were −100. */
|
|
112
|
+
.nps-marker { display: none; position: absolute; top: -3px; width: 4px; height: 12px; margin-left: -2px;
|
|
113
|
+
border-radius: 2px; background: var(--text); box-shadow: 0 0 0 2px var(--surface); }
|
|
114
|
+
.nps-marker.placed { display: block; }
|
|
115
|
+
.nps-meter-ticks { position: relative; height: 14px; margin-top: 3px; color: var(--muted); font-size: 11px; }
|
|
116
|
+
.nps-meter-ticks span { position: absolute; transform: translateX(-50%); }
|
|
117
|
+
.nps-meter-ticks .tick-min { left: 0; transform: none; }
|
|
118
|
+
.nps-meter-ticks .tick-zero { left: 50%; }
|
|
119
|
+
.nps-meter-ticks .tick-good { left: 65%; }
|
|
120
|
+
.nps-meter-ticks .tick-great { left: 85%; }
|
|
121
|
+
.nps-meter-ticks .tick-max { left: 100%; transform: translateX(-100%); }
|
|
122
|
+
.nps-sample { margin: 8px 0 0; font-size: 12px; }
|
|
123
|
+
|
|
124
|
+
/* The formula and the benchmarks, tucked behind a disclosure. */
|
|
125
|
+
.nps-guide { flex: 0 0 auto; margin-bottom: 14px; background: var(--surface);
|
|
126
|
+
border: 1px solid var(--border); border-radius: 12px; }
|
|
127
|
+
.nps-guide summary { padding: 10px 16px; color: var(--muted); font-size: 13px; cursor: pointer; }
|
|
128
|
+
.nps-guide summary:hover { color: var(--text); }
|
|
129
|
+
.nps-guide[open] summary { border-bottom: 1px solid var(--border); }
|
|
130
|
+
.nps-guide-body { padding: 12px 16px 14px; font-size: 13px; }
|
|
131
|
+
.nps-guide-body p { margin: 0; max-width: 78ch; }
|
|
132
|
+
.nps-bands { display: grid; grid-template-columns: max-content max-content 1fr; gap: 8px 14px;
|
|
133
|
+
margin: 12px 0; padding: 0; list-style: none; align-items: baseline; }
|
|
134
|
+
.nps-bands li { display: contents; }
|
|
135
|
+
.nps-bands b { font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
89
136
|
.best-line-form textarea { width: 100%; padding: 8px; border: 1px solid var(--border); border-radius: 8px;
|
|
90
137
|
background: var(--surface); color: var(--text); font: inherit; resize: vertical; }
|
|
91
138
|
.best-line-hint { margin: 2px 0 6px; }
|
|
@@ -183,5 +230,9 @@ img.avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; }
|
|
|
183
230
|
.stat-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
184
231
|
.stat { min-width: 0; padding: 10px 12px; }
|
|
185
232
|
.stat b { font-size: 22px; }
|
|
233
|
+
.stat-nps { grid-column: 1 / -1; }
|
|
234
|
+
.nps-bands { grid-template-columns: max-content 1fr; }
|
|
235
|
+
.nps-bands b { grid-column: 2; }
|
|
236
|
+
.nps-bands li > .muted { grid-column: 1 / -1; }
|
|
186
237
|
.share-link { flex: 1 0 100%; margin-left: 0; text-align: center; }
|
|
187
238
|
}
|
|
@@ -21,6 +21,22 @@
|
|
|
21
21
|
}
|
|
22
22
|
}, true);
|
|
23
23
|
|
|
24
|
+
// Places the NPS score marker on the −100..100 scale. The position is a data
|
|
25
|
+
// attribute rather than an inline style, which a strict style-src refuses.
|
|
26
|
+
function placeNpsMarkers() {
|
|
27
|
+
var markers = document.querySelectorAll("[data-nps-marker]");
|
|
28
|
+
for (var i = 0; i < markers.length; i++) {
|
|
29
|
+
var score = parseFloat(markers[i].getAttribute("data-nps-marker"));
|
|
30
|
+
if (isNaN(score)) continue;
|
|
31
|
+
markers[i].style.left = ((score + 100) / 2) + "%";
|
|
32
|
+
markers[i].classList.add("placed");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
document.addEventListener("DOMContentLoaded", placeNpsMarkers);
|
|
37
|
+
document.addEventListener("turbo:load", placeNpsMarkers);
|
|
38
|
+
placeNpsMarkers();
|
|
39
|
+
|
|
24
40
|
document.addEventListener("change", function (event) {
|
|
25
41
|
var control = event.target;
|
|
26
42
|
if (!control || !control.dataset || !control.dataset.autosubmit || !control.form) return;
|
data/lib/testimonials/seeds.rb
CHANGED
|
@@ -90,7 +90,8 @@ module Testimonials
|
|
|
90
90
|
def self.load!(tenant: nil)
|
|
91
91
|
{
|
|
92
92
|
testimonials: load_testimonials!(tenant: tenant),
|
|
93
|
-
|
|
93
|
+
# An install run with --skip-nps has no table to seed into.
|
|
94
|
+
nps_responses: (Testimonials.config.nps ? load_nps_responses!(tenant: tenant) : []),
|
|
94
95
|
prompt_events: load_prompt_events!(tenant: tenant)
|
|
95
96
|
}
|
|
96
97
|
end
|
data/lib/testimonials/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: testimonials
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -97,7 +97,9 @@ files:
|
|
|
97
97
|
- config/routes.rb
|
|
98
98
|
- lib/generators/testimonials/install/install_generator.rb
|
|
99
99
|
- lib/generators/testimonials/install/templates/create_testimonials_tables.rb.tt
|
|
100
|
-
- lib/generators/testimonials/install/templates/initializer.rb
|
|
100
|
+
- lib/generators/testimonials/install/templates/initializer.rb.tt
|
|
101
|
+
- lib/generators/testimonials/nps/nps_generator.rb
|
|
102
|
+
- lib/generators/testimonials/nps/templates/create_testimonials_nps_responses.rb.tt
|
|
101
103
|
- lib/generators/testimonials/tenant/templates/add_tenant_to_testimonials.rb.tt
|
|
102
104
|
- lib/generators/testimonials/tenant/tenant_generator.rb
|
|
103
105
|
- lib/tasks/testimonials_tasks.rake
|