@mundogamernetwork/shared-ui 1.16.22 → 1.16.23

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.
@@ -0,0 +1,219 @@
1
+ import { ref } from "vue";
2
+ import {
3
+ requestBuildAccess,
4
+ initiateBuildNda,
5
+ fetchBuildAssets,
6
+ fetchBuildDownloadUrl,
7
+ type PlaytestBuildGrant,
8
+ type PlaytestBuildAsset,
9
+ } from "../services/playtestTesterService";
10
+ import {
11
+ fetchSigningSession,
12
+ requestSigningOtp,
13
+ verifySigningOtp,
14
+ acceptSigningConsent,
15
+ submitSignature,
16
+ type EsignatureEnvelope,
17
+ type EsignatureSignerState,
18
+ } from "../services/esignatureService";
19
+
20
+ /**
21
+ * Build-access + NDA-sign state for a single campaign. Mirrors
22
+ * usePlaytestAccessGrant's shape (loading/submitting/genericError refs), but
23
+ * one level deeper: this owns BOTH the Playtest-specific grant/asset state
24
+ * AND drives the generic ESignature sign flow through a sign_token once the
25
+ * NDA envelope exists — a tester never talks to /esignature/* directly.
26
+ */
27
+ export function usePlaytestBuildAccess(campaignId: number) {
28
+ const loading = ref(false);
29
+ const submitting = ref(false);
30
+ const genericError = ref<string | null>(null);
31
+
32
+ const grant = ref<PlaytestBuildGrant | null>(null);
33
+ const assets = ref<PlaytestBuildAsset[]>([]);
34
+ const signToken = ref<string | null>(null);
35
+ const envelope = ref<EsignatureEnvelope | null>(null);
36
+ const signer = ref<EsignatureSignerState | null>(null);
37
+ // True once an OTP has been sent for the current attempt — same
38
+ // UI-responsiveness reasoning as usePlaytestAccessGrant.waiverInitiated.
39
+ const otpInitiated = ref(false);
40
+
41
+ function resetErrors() {
42
+ genericError.value = null;
43
+ }
44
+
45
+ function errorMessage(e: any, fallback: string): string {
46
+ return e?.response?.data?.message ?? e?.message ?? fallback;
47
+ }
48
+
49
+ /** Loads the ready build list — call this on mount regardless of grant status, so the panel can decide whether to show anything at all. */
50
+ async function loadAssets() {
51
+ loading.value = true;
52
+ try {
53
+ const res = await fetchBuildAssets(campaignId);
54
+ assets.value = res.data.data;
55
+ return assets.value;
56
+ } finally {
57
+ loading.value = false;
58
+ }
59
+ }
60
+
61
+ /** Idempotent — safe to call every time the panel mounts. No-ops (server-side) into returning the existing grant if one is already there. */
62
+ async function requestAccess() {
63
+ resetErrors();
64
+ submitting.value = true;
65
+ try {
66
+ const res = await requestBuildAccess(campaignId);
67
+ grant.value = res.data.data;
68
+ return grant.value;
69
+ } catch (e: any) {
70
+ genericError.value = errorMessage(e, "Could not request build access. Please try again.");
71
+ return null;
72
+ } finally {
73
+ submitting.value = false;
74
+ }
75
+ }
76
+
77
+ async function initiateNda() {
78
+ if (!grant.value) return null;
79
+ resetErrors();
80
+ submitting.value = true;
81
+ try {
82
+ const res = await initiateBuildNda(grant.value.id);
83
+ signToken.value = res.data.data.sign_token;
84
+ if (signToken.value) await loadSigningSession();
85
+ return signToken.value;
86
+ } catch (e: any) {
87
+ genericError.value = errorMessage(e, "Could not start the NDA signing process.");
88
+ return null;
89
+ } finally {
90
+ submitting.value = false;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * The single entry point the UI calls for both "send code" (first time,
96
+ * no envelope exists yet — creates one first) and "resend code" (envelope
97
+ * already exists) — one click either way, the component never needs to
98
+ * know which case it is.
99
+ */
100
+ async function sendCode() {
101
+ if (!signToken.value) {
102
+ const token = await initiateNda();
103
+ if (!token) return false;
104
+ }
105
+ return sendOtp();
106
+ }
107
+
108
+ async function loadSigningSession() {
109
+ if (!signToken.value) return;
110
+ const res = await fetchSigningSession(signToken.value);
111
+ envelope.value = res.data.data.envelope;
112
+ signer.value = res.data.data.signer;
113
+ }
114
+
115
+ async function sendOtp() {
116
+ if (!signToken.value) return false;
117
+ resetErrors();
118
+ submitting.value = true;
119
+ try {
120
+ await requestSigningOtp(signToken.value);
121
+ otpInitiated.value = true;
122
+ return true;
123
+ } catch (e: any) {
124
+ genericError.value = errorMessage(e, "Could not send the verification code. Please try again.");
125
+ return false;
126
+ } finally {
127
+ submitting.value = false;
128
+ }
129
+ }
130
+
131
+ async function verifyOtp(otpCode: string) {
132
+ if (!signToken.value) return false;
133
+ resetErrors();
134
+ submitting.value = true;
135
+ try {
136
+ await verifySigningOtp(signToken.value, otpCode);
137
+ await loadSigningSession();
138
+ return true;
139
+ } catch (e: any) {
140
+ genericError.value = errorMessage(e, "Invalid or expired code.");
141
+ return false;
142
+ } finally {
143
+ submitting.value = false;
144
+ }
145
+ }
146
+
147
+ async function acceptConsent() {
148
+ if (!signToken.value) return false;
149
+ resetErrors();
150
+ submitting.value = true;
151
+ try {
152
+ await acceptSigningConsent(signToken.value);
153
+ await loadSigningSession();
154
+ return true;
155
+ } catch (e: any) {
156
+ genericError.value = errorMessage(e, "Could not accept the consent. Please try again.");
157
+ return false;
158
+ } finally {
159
+ submitting.value = false;
160
+ }
161
+ }
162
+
163
+ /** Signs, then re-fetches the grant — the PlaytestBuildGrantEnvelopeObserver flips it to nda_signed the instant the envelope completes. */
164
+ async function sign() {
165
+ if (!signToken.value) return false;
166
+ resetErrors();
167
+ submitting.value = true;
168
+ try {
169
+ await submitSignature(signToken.value);
170
+ await loadSigningSession();
171
+ await requestAccess(); // re-fetch grant, now nda_signed
172
+ return true;
173
+ } catch (e: any) {
174
+ genericError.value = errorMessage(e, "Could not submit the signature. Please try again.");
175
+ return false;
176
+ } finally {
177
+ submitting.value = false;
178
+ }
179
+ }
180
+
181
+ /** Mints a fresh URL every call — never reuse a previously returned one. */
182
+ async function getDownloadUrl(assetId: number) {
183
+ if (!grant.value) return null;
184
+ resetErrors();
185
+ submitting.value = true;
186
+ try {
187
+ const res = await fetchBuildDownloadUrl(grant.value.id, assetId);
188
+ return res.data.data.url;
189
+ } catch (e: any) {
190
+ genericError.value = errorMessage(e, "Could not generate a download link. Please try again.");
191
+ return null;
192
+ } finally {
193
+ submitting.value = false;
194
+ }
195
+ }
196
+
197
+ return {
198
+ loading,
199
+ submitting,
200
+ genericError,
201
+ grant,
202
+ assets,
203
+ envelope,
204
+ signer,
205
+ otpInitiated,
206
+ loadAssets,
207
+ requestAccess,
208
+ initiateNda,
209
+ sendCode,
210
+ sendOtp,
211
+ verifyOtp,
212
+ acceptConsent,
213
+ sign,
214
+ getDownloadUrl,
215
+ resetErrors,
216
+ };
217
+ }
218
+
219
+ export type PlaytestBuildAccessComposable = ReturnType<typeof usePlaytestBuildAccess>;
package/locales/de.json CHANGED
@@ -1,4 +1,136 @@
1
1
  {
2
+ "playtest": {
3
+ "access_grant": {
4
+ "copied": "Kopiert",
5
+ "copy": "Kopieren",
6
+ "enter_code": "Gib den 6-stelligen Code ein, den wir dir per E-Mail geschickt haben",
7
+ "open_link": "Link öffnen",
8
+ "resend_code": "Code erneut senden",
9
+ "reveal": "Anzeigen",
10
+ "send_code": "Bestätigungscode senden",
11
+ "sending_code": "Code wird gesendet…",
12
+ "verify": "Bestätigen",
13
+ "verifying": "Wird bestätigt…",
14
+ "waiver_title": "Unterschreibe den Haftungsausschluss, um deinen Spielzugang freizuschalten",
15
+ "your_key": "Dein Spielschlüssel",
16
+ "your_link": "Dein Download-Link"
17
+ },
18
+ "build_access": {
19
+ "accept_consent": "Akzeptieren und fortfahren",
20
+ "accepting": "Wird gesendet…",
21
+ "builds_title": "Verfügbare Builds",
22
+ "consent_checkbox": "Ich habe die obige Vereinbarung gelesen und verstanden",
23
+ "download": "Herunterladen",
24
+ "enter_code": "Gib den 6-stelligen Code ein, den wir dir per E-Mail geschickt haben",
25
+ "nda_title": "Unterschreibe die NDA, um die Build freizuschalten",
26
+ "no_builds": "Noch keine Builds verfügbar",
27
+ "resend_code": "Code erneut senden",
28
+ "revoked": "Dein Build-Zugang wurde widerrufen",
29
+ "send_code": "Bestätigungscode senden",
30
+ "sending_code": "Code wird gesendet…",
31
+ "sign": "Unterschreiben",
32
+ "signing": "Wird unterschrieben…",
33
+ "verify": "Bestätigen",
34
+ "verifying": "Wird bestätigt…",
35
+ "view_pdf": "NDA-Dokument ansehen"
36
+ },
37
+ "accessibility": {
38
+ "notes_required": "Notizen (erforderlich bei Nichtbestehen)",
39
+ "uses_accommodation": "Ich bin persönlich auf diese Hilfestellung angewiesen"
40
+ },
41
+ "baseline": {
42
+ "feedback_optional": "Dein Feedback (optional)",
43
+ "feedback_required": "Dein Feedback (erforderlich)"
44
+ },
45
+ "brief": {
46
+ "ineligible_generic": "Du erfüllst derzeit nicht die Voraussetzungen für diesen Playtest.",
47
+ "not_found": "Diese Playtest-Kampagne konnte nicht gefunden werden.",
48
+ "paid_on_acceptance": "Wird ausgezahlt, sobald das Studio deine Antwort akzeptiert, nicht beim Einreichen.",
49
+ "reward": "Belohnung"
50
+ },
51
+ "compliance": {
52
+ "attach_evidence": "Nachweis anhängen (Screenshot/Video)",
53
+ "notes_required": "Notizen (erforderlich bei Nichtbestehen)",
54
+ "uploaded": "Angehängt",
55
+ "uploading": "Wird hochgeladen…"
56
+ },
57
+ "concept_poll": {
58
+ "emoji_label": "Wähle deine Reaktion",
59
+ "five_second_intro": "Du siehst das/die Bild(er) {seconds} Sekunden lang und antwortest danach aus dem Gedächtnis.",
60
+ "five_second_start": "Start",
61
+ "ranked_hint": "Ordne diese von deiner bevorzugtesten zur am wenigsten bevorzugten (nutze die Pfeile).",
62
+ "reason_label": "Warum hast du das gewählt? (erforderlich)",
63
+ "reason_min_length": "Bitte gib mindestens 3 Zeichen ein.",
64
+ "recall_label": "Woran erinnerst du dich? (erforderlich)",
65
+ "stars_label": "Deine Bewertung",
66
+ "submit_vote": "Stimme abgeben"
67
+ },
68
+ "flow": {
69
+ "criteria_completed": "Kriterien abgeschlossen",
70
+ "submit_checklist": "Checkliste absenden",
71
+ "submit_response": "Antwort absenden"
72
+ },
73
+ "lqa": {
74
+ "actual_text": "Tatsächlicher Text",
75
+ "attach_screenshot": "Screenshot/Video anhängen (erforderlich)",
76
+ "description": "Beschreibung (erforderlich)",
77
+ "evidence_hint": "Ein Screenshot oder Videoclip des tatsächlichen Builds ist erforderlich, damit das Studio das Problem überprüfen kann.",
78
+ "expected_text": "Erwarteter Text",
79
+ "file_issue": "Problem melden",
80
+ "filed_issues": "In dieser Sitzung gemeldete Probleme",
81
+ "issue_type": "Problemtyp",
82
+ "location": "Ort (Bildschirm/Szene, optional)",
83
+ "no_issues_yet": "Noch keine Probleme gemeldet.",
84
+ "responses_progress": "Bisherige Antworten",
85
+ "select_locale": "Sprache / Build im Fokus",
86
+ "select_locale_placeholder": "Sprache auswählen…",
87
+ "select_severity_placeholder": "Auswählen…",
88
+ "select_type_placeholder": "Auswählen…",
89
+ "severity": "Schweregrad",
90
+ "uploaded": "Angehängt",
91
+ "uploading": "Wird hochgeladen…"
92
+ },
93
+ "moderated": {
94
+ "cancel_session": "Abbrechen",
95
+ "claim_slot": "Diesen Termin reservieren",
96
+ "confirm_attendance": "Teilnahme bestätigen",
97
+ "confirmed": "bestätigt",
98
+ "join_call": "Anruf beitreten",
99
+ "link_pending": "Der Anruflink erscheint hier kurz vor der Sitzung.",
100
+ "no_session": "Keine Sitzung ausgewählt.",
101
+ "session_title": "Moderierte Sitzung",
102
+ "you_confirmed": "Du hast deine Teilnahme bestätigt"
103
+ },
104
+ "pending": {
105
+ "browse_more": "Weitere Playtests durchsuchen",
106
+ "ineligible_message": "Seit deinem Start hat sich etwas geändert — die Anforderungen dieser Kampagne werden nicht mehr erfüllt.",
107
+ "ineligible_title": "Du erfüllst die Voraussetzungen für diesen Playtest nicht mehr",
108
+ "message": "Danke fürs Testen! Das Studio wird deine Antwort prüfen — du wirst bezahlt, wenn sie akzeptiert wird.",
109
+ "title": "Antwort gesendet"
110
+ },
111
+ "video_capture": {
112
+ "continue_anyway": "Trotzdem fortfahren",
113
+ "hint": "Zeichne deinen Bildschirm auf, während du spielst. Du kannst dabei Notizen mit Zeitstempel hinzufügen.",
114
+ "mark": "Markieren",
115
+ "marker_save_failed": "Antwort gesendet, aber {v} Notiz(en) konnten nicht gespeichert werden.",
116
+ "processing_hint": "Deine Aufnahme wurde hochgeladen und wird verarbeitet. Sende ab, wenn du bereit bist — wir bestätigen im Hintergrund, dass sie fertig ist.",
117
+ "recording": "Aufnahme läuft…",
118
+ "retry_markers": "Notizen erneut speichern",
119
+ "retrying": "Wird erneut versucht…",
120
+ "review_hint": "Aufnahme erfasst ({v}). Lade sie hoch, um deine Antwort zu senden.",
121
+ "start": "Aufnahme starten",
122
+ "stop": "Aufnahme stoppen",
123
+ "submit": "Antwort absenden",
124
+ "submitting": "Wird gesendet…",
125
+ "upload": "Aufnahme hochladen",
126
+ "uploading": "Wird hochgeladen… {v}%"
127
+ },
128
+ "video_player": {
129
+ "open_direct": "Aufnahme öffnen",
130
+ "playback_error": "Dieser Browser kann die Aufnahme nicht direkt abspielen — versuche es mit Safari oder öffne den Datei-Link unten.",
131
+ "processing": "Die Aufnahme wird noch verarbeitet — schau in Kürze noch einmal vorbei."
132
+ }
133
+ },
2
134
  "keys": {
3
135
  "notif_prompt_title": "Benachrichtigungen für neue Key-Kampagnen erhalten?",
4
136
  "notif_prompt_desc": "Wir benachrichtigen Sie per E-Mail und Push, wenn eine neue Key-Kampagne startet.",
@@ -210,6 +342,7 @@
210
342
  "your_key": "Dein Schlüssel",
211
343
  "copy": "Kopieren",
212
344
  "copied": "Kopiert!",
345
+ "extra_codes": "Diese Anfrage wurde für {count} Keys genehmigt — hier sind alle.",
213
346
  "redeem_on_platform": "Löse diesen Schlüssel auf der entsprechenden Plattform ein.",
214
347
  "revealing": "Enthülle...",
215
348
  "status": {
package/locales/en.json CHANGED
@@ -1,4 +1,136 @@
1
1
  {
2
+ "playtest": {
3
+ "access_grant": {
4
+ "copied": "Copied",
5
+ "copy": "Copy",
6
+ "enter_code": "Enter the 6-digit code we emailed you",
7
+ "open_link": "Open link",
8
+ "resend_code": "Resend code",
9
+ "reveal": "Reveal",
10
+ "send_code": "Send verification code",
11
+ "sending_code": "Sending code…",
12
+ "verify": "Verify",
13
+ "verifying": "Verifying…",
14
+ "waiver_title": "Sign the liability waiver to unlock your game access",
15
+ "your_key": "Your game key",
16
+ "your_link": "Your download link"
17
+ },
18
+ "build_access": {
19
+ "accept_consent": "Accept and continue",
20
+ "accepting": "Submitting…",
21
+ "builds_title": "Available builds",
22
+ "consent_checkbox": "I have read and understood the agreement above",
23
+ "download": "Download",
24
+ "enter_code": "Enter the 6-digit code we emailed you",
25
+ "nda_title": "Sign the NDA to unlock the build",
26
+ "no_builds": "No builds available yet",
27
+ "resend_code": "Resend code",
28
+ "revoked": "Your build access has been revoked",
29
+ "send_code": "Send verification code",
30
+ "sending_code": "Sending code…",
31
+ "sign": "Sign",
32
+ "signing": "Signing…",
33
+ "verify": "Verify",
34
+ "verifying": "Verifying…",
35
+ "view_pdf": "View NDA document"
36
+ },
37
+ "accessibility": {
38
+ "notes_required": "Notes (required when failing)",
39
+ "uses_accommodation": "I personally rely on this accommodation"
40
+ },
41
+ "baseline": {
42
+ "feedback_optional": "Your feedback (optional)",
43
+ "feedback_required": "Your feedback (required)"
44
+ },
45
+ "brief": {
46
+ "ineligible_generic": "You don't currently qualify for this playtest.",
47
+ "not_found": "This playtest campaign could not be found.",
48
+ "paid_on_acceptance": "Paid when the studio accepts your response, not on submission.",
49
+ "reward": "Reward"
50
+ },
51
+ "compliance": {
52
+ "attach_evidence": "Attach evidence (screenshot/video)",
53
+ "notes_required": "Notes (required when failing)",
54
+ "uploaded": "Attached",
55
+ "uploading": "Uploading…"
56
+ },
57
+ "concept_poll": {
58
+ "emoji_label": "Pick your reaction",
59
+ "five_second_intro": "You'll see the image(s) for {seconds} seconds, then answer from memory.",
60
+ "five_second_start": "Start",
61
+ "ranked_hint": "Order these from your most to least preferred (use the arrows).",
62
+ "reason_label": "Why did you choose this? (required)",
63
+ "reason_min_length": "Please write at least 3 characters.",
64
+ "recall_label": "What do you remember? (required)",
65
+ "stars_label": "Your rating",
66
+ "submit_vote": "Submit vote"
67
+ },
68
+ "flow": {
69
+ "criteria_completed": "criteria completed",
70
+ "submit_checklist": "Submit checklist",
71
+ "submit_response": "Submit response"
72
+ },
73
+ "lqa": {
74
+ "actual_text": "Actual text",
75
+ "attach_screenshot": "Attach screenshot/video (required)",
76
+ "description": "Description (required)",
77
+ "evidence_hint": "A screenshot or clip of the actual build is required so the studio can verify the issue.",
78
+ "expected_text": "Expected text",
79
+ "file_issue": "File issue",
80
+ "filed_issues": "Issues filed this session",
81
+ "issue_type": "Issue type",
82
+ "location": "Location (screen/scene, optional)",
83
+ "no_issues_yet": "No issues filed yet.",
84
+ "responses_progress": "Responses so far",
85
+ "select_locale": "Locale / build in scope",
86
+ "select_locale_placeholder": "Select a locale…",
87
+ "select_severity_placeholder": "Select…",
88
+ "select_type_placeholder": "Select…",
89
+ "severity": "Severity",
90
+ "uploaded": "Attached",
91
+ "uploading": "Uploading…"
92
+ },
93
+ "moderated": {
94
+ "cancel_session": "Cancel",
95
+ "claim_slot": "Claim this slot",
96
+ "confirm_attendance": "Confirm attendance",
97
+ "confirmed": "confirmed",
98
+ "join_call": "Join call",
99
+ "link_pending": "The call link will appear here closer to the session.",
100
+ "no_session": "No session selected.",
101
+ "session_title": "Moderated session",
102
+ "you_confirmed": "You've confirmed attendance"
103
+ },
104
+ "pending": {
105
+ "browse_more": "Browse more playtests",
106
+ "ineligible_message": "Something changed since you started — this campaign's requirements are no longer met.",
107
+ "ineligible_title": "You no longer qualify for this playtest",
108
+ "message": "Thanks for testing! The studio will review your response — you'll be paid if it's accepted.",
109
+ "title": "Response submitted"
110
+ },
111
+ "video_capture": {
112
+ "continue_anyway": "Continue anyway",
113
+ "hint": "Record your screen while you play. You can drop timestamped notes as you go.",
114
+ "mark": "Mark",
115
+ "marker_save_failed": "Response submitted, but {v} note(s) failed to save.",
116
+ "processing_hint": "Your recording is uploaded and processing. Submit when you're ready — we'll finish confirming it's ready in the background.",
117
+ "recording": "Recording…",
118
+ "retry_markers": "Retry saving notes",
119
+ "retrying": "Retrying…",
120
+ "review_hint": "Recording captured ({v}). Upload it to submit your response.",
121
+ "start": "Start recording",
122
+ "stop": "Stop recording",
123
+ "submit": "Submit response",
124
+ "submitting": "Submitting…",
125
+ "upload": "Upload recording",
126
+ "uploading": "Uploading… {v}%"
127
+ },
128
+ "video_player": {
129
+ "open_direct": "Open recording",
130
+ "playback_error": "This browser can't play the recording directly — try Safari, or open the file link below.",
131
+ "processing": "Recording is still processing — check back shortly."
132
+ }
133
+ },
2
134
  "keys": {
3
135
  "notif_prompt_title": "Get alerts for new key campaigns?",
4
136
  "notif_prompt_desc": "We'll notify you by email and push whenever a new key campaign opens.",
@@ -210,6 +342,7 @@
210
342
  "your_key": "Your Key",
211
343
  "copy": "Copy",
212
344
  "copied": "Copied!",
345
+ "extra_codes": "This request was approved for {count} keys — here are all of them.",
213
346
  "redeem_on_platform": "Redeem this key on the platform where you received it.",
214
347
  "revealing": "Revealing...",
215
348
  "status": {
package/locales/es.json CHANGED
@@ -1,4 +1,136 @@
1
1
  {
2
+ "playtest": {
3
+ "access_grant": {
4
+ "copied": "Copiado",
5
+ "copy": "Copiar",
6
+ "enter_code": "Ingresa el código de 6 dígitos que te enviamos por correo",
7
+ "open_link": "Abrir enlace",
8
+ "resend_code": "Reenviar código",
9
+ "reveal": "Revelar",
10
+ "send_code": "Enviar código de verificación",
11
+ "sending_code": "Enviando código…",
12
+ "verify": "Verificar",
13
+ "verifying": "Verificando…",
14
+ "waiver_title": "Firma el descargo de responsabilidad para desbloquear tu acceso al juego",
15
+ "your_key": "Tu clave del juego",
16
+ "your_link": "Tu enlace de descarga"
17
+ },
18
+ "build_access": {
19
+ "accept_consent": "Aceptar y continuar",
20
+ "accepting": "Enviando…",
21
+ "builds_title": "Builds disponibles",
22
+ "consent_checkbox": "He leído y entendido el acuerdo anterior",
23
+ "download": "Descargar",
24
+ "enter_code": "Ingresa el código de 6 dígitos que te enviamos por correo",
25
+ "nda_title": "Firma el NDA para desbloquear la build",
26
+ "no_builds": "Todavía no hay builds disponibles",
27
+ "resend_code": "Reenviar código",
28
+ "revoked": "Tu acceso a la build ha sido revocado",
29
+ "send_code": "Enviar código de verificación",
30
+ "sending_code": "Enviando código…",
31
+ "sign": "Firmar",
32
+ "signing": "Firmando…",
33
+ "verify": "Verificar",
34
+ "verifying": "Verificando…",
35
+ "view_pdf": "Ver documento del NDA"
36
+ },
37
+ "accessibility": {
38
+ "notes_required": "Notas (obligatorias si no aprueba)",
39
+ "uses_accommodation": "Yo personalmente dependo de esta adaptación"
40
+ },
41
+ "baseline": {
42
+ "feedback_optional": "Tu comentario (opcional)",
43
+ "feedback_required": "Tu comentario (obligatorio)"
44
+ },
45
+ "brief": {
46
+ "ineligible_generic": "Actualmente no eres elegible para este playtest.",
47
+ "not_found": "No se pudo encontrar esta campaña de playtest.",
48
+ "paid_on_acceptance": "Se paga cuando el estudio acepta tu respuesta, no al enviarla.",
49
+ "reward": "Recompensa"
50
+ },
51
+ "compliance": {
52
+ "attach_evidence": "Adjuntar evidencia (captura de pantalla/video)",
53
+ "notes_required": "Notas (obligatorias si no aprueba)",
54
+ "uploaded": "Adjuntado",
55
+ "uploading": "Subiendo…"
56
+ },
57
+ "concept_poll": {
58
+ "emoji_label": "Elige tu reacción",
59
+ "five_second_intro": "Verás la(s) imagen(es) durante {seconds} segundos y luego responderás de memoria.",
60
+ "five_second_start": "Comenzar",
61
+ "ranked_hint": "Ordena de lo que más prefieres a lo que menos prefieres (usa las flechas).",
62
+ "reason_label": "¿Por qué elegiste esto? (obligatorio)",
63
+ "reason_min_length": "Escribe al menos 3 caracteres.",
64
+ "recall_label": "¿Qué recuerdas? (obligatorio)",
65
+ "stars_label": "Tu calificación",
66
+ "submit_vote": "Enviar voto"
67
+ },
68
+ "flow": {
69
+ "criteria_completed": "criterios completados",
70
+ "submit_checklist": "Enviar checklist",
71
+ "submit_response": "Enviar respuesta"
72
+ },
73
+ "lqa": {
74
+ "actual_text": "Texto actual",
75
+ "attach_screenshot": "Adjuntar captura de pantalla/video (obligatorio)",
76
+ "description": "Descripción (obligatoria)",
77
+ "evidence_hint": "Se requiere una captura de pantalla o video del build real para que el estudio pueda verificar el problema.",
78
+ "expected_text": "Texto esperado",
79
+ "file_issue": "Registrar problema",
80
+ "filed_issues": "Problemas registrados en esta sesión",
81
+ "issue_type": "Tipo de problema",
82
+ "location": "Ubicación (pantalla/escena, opcional)",
83
+ "no_issues_yet": "Aún no se han registrado problemas.",
84
+ "responses_progress": "Respuestas hasta ahora",
85
+ "select_locale": "Idioma / build en alcance",
86
+ "select_locale_placeholder": "Selecciona un idioma…",
87
+ "select_severity_placeholder": "Selecciona…",
88
+ "select_type_placeholder": "Selecciona…",
89
+ "severity": "Severidad",
90
+ "uploaded": "Adjuntado",
91
+ "uploading": "Subiendo…"
92
+ },
93
+ "moderated": {
94
+ "cancel_session": "Cancelar",
95
+ "claim_slot": "Reservar este horario",
96
+ "confirm_attendance": "Confirmar asistencia",
97
+ "confirmed": "confirmado",
98
+ "join_call": "Unirse a la llamada",
99
+ "link_pending": "El enlace de la llamada aparecerá aquí cerca de la sesión.",
100
+ "no_session": "No hay ninguna sesión seleccionada.",
101
+ "session_title": "Sesión moderada",
102
+ "you_confirmed": "Has confirmado tu asistencia"
103
+ },
104
+ "pending": {
105
+ "browse_more": "Ver más playtests",
106
+ "ineligible_message": "Algo cambió desde que empezaste — ya no se cumplen los requisitos de esta campaña.",
107
+ "ineligible_title": "Ya no eres elegible para este playtest",
108
+ "message": "¡Gracias por probar! El estudio revisará tu respuesta — se te pagará si es aceptada.",
109
+ "title": "Respuesta enviada"
110
+ },
111
+ "video_capture": {
112
+ "continue_anyway": "Continuar de todos modos",
113
+ "hint": "Graba tu pantalla mientras juegas. Puedes agregar notas con marca de tiempo sobre la marcha.",
114
+ "mark": "Marcar",
115
+ "marker_save_failed": "Respuesta enviada, pero {v} nota(s) no se pudieron guardar.",
116
+ "processing_hint": "Tu grabación se subió y está en proceso. Envíala cuando estés listo — terminaremos de confirmar que está lista en segundo plano.",
117
+ "recording": "Grabando…",
118
+ "retry_markers": "Reintentar guardar notas",
119
+ "retrying": "Reintentando…",
120
+ "review_hint": "Grabación capturada ({v}). Súbela para enviar tu respuesta.",
121
+ "start": "Iniciar grabación",
122
+ "stop": "Detener grabación",
123
+ "submit": "Enviar respuesta",
124
+ "submitting": "Enviando…",
125
+ "upload": "Subir grabación",
126
+ "uploading": "Subiendo… {v}%"
127
+ },
128
+ "video_player": {
129
+ "open_direct": "Abrir grabación",
130
+ "playback_error": "Este navegador no puede reproducir la grabación directamente — prueba con Safari o abre el enlace del archivo a continuación.",
131
+ "processing": "La grabación todavía se está procesando — vuelve a comprobarlo en unos momentos."
132
+ }
133
+ },
2
134
  "keys": {
3
135
  "notif_prompt_title": "¿Recibir alertas de nuevas campañas de claves?",
4
136
  "notif_prompt_desc": "Te avisaremos por correo y push cuando se abra una nueva campaña.",
@@ -210,6 +342,7 @@
210
342
  "your_key": "Tu Clave",
211
343
  "copy": "Copiar código",
212
344
  "copied": "¡Código copiado!",
345
+ "extra_codes": "Esta solicitud fue aprobada para {count} claves: aquí están todas.",
213
346
  "redeem_on_platform": "Canjea esta clave en la plataforma donde la recibiste.",
214
347
  "revealing": "Revelando...",
215
348
  "status": {