@jjlmoya/utils-streaming 1.2.0 → 1.3.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.
Files changed (43) hide show
  1. package/package.json +2 -1
  2. package/src/category/i18n/de.ts +134 -0
  3. package/src/category/i18n/id.ts +134 -0
  4. package/src/category/i18n/it.ts +134 -0
  5. package/src/category/i18n/ja.ts +134 -0
  6. package/src/category/i18n/ko.ts +134 -0
  7. package/src/category/i18n/nl.ts +134 -0
  8. package/src/category/i18n/pl.ts +134 -0
  9. package/src/category/i18n/pt.ts +134 -0
  10. package/src/category/i18n/ru.ts +134 -0
  11. package/src/category/i18n/sv.ts +134 -0
  12. package/src/category/i18n/tr.ts +134 -0
  13. package/src/category/i18n/zh.ts +134 -0
  14. package/src/category/index.ts +12 -0
  15. package/src/tests/i18n_coverage.test.ts +36 -0
  16. package/src/tests/locale_completeness.test.ts +1 -1
  17. package/src/tests/slug_uniqueness.test.ts +81 -0
  18. package/src/tool/sorteo/i18n/de.ts +256 -0
  19. package/src/tool/sorteo/i18n/id.ts +256 -0
  20. package/src/tool/sorteo/i18n/it.ts +256 -0
  21. package/src/tool/sorteo/i18n/ja.ts +256 -0
  22. package/src/tool/sorteo/i18n/ko.ts +256 -0
  23. package/src/tool/sorteo/i18n/nl.ts +256 -0
  24. package/src/tool/sorteo/i18n/pl.ts +256 -0
  25. package/src/tool/sorteo/i18n/pt.ts +256 -0
  26. package/src/tool/sorteo/i18n/ru.ts +256 -0
  27. package/src/tool/sorteo/i18n/sv.ts +256 -0
  28. package/src/tool/sorteo/i18n/tr.ts +256 -0
  29. package/src/tool/sorteo/i18n/zh.ts +256 -0
  30. package/src/tool/sorteo/index.ts +12 -0
  31. package/src/tool/tebasCheck/i18n/de.ts +256 -0
  32. package/src/tool/tebasCheck/i18n/id.ts +256 -0
  33. package/src/tool/tebasCheck/i18n/it.ts +256 -0
  34. package/src/tool/tebasCheck/i18n/ja.ts +256 -0
  35. package/src/tool/tebasCheck/i18n/ko.ts +256 -0
  36. package/src/tool/tebasCheck/i18n/nl.ts +256 -0
  37. package/src/tool/tebasCheck/i18n/pl.ts +256 -0
  38. package/src/tool/tebasCheck/i18n/pt.ts +256 -0
  39. package/src/tool/tebasCheck/i18n/ru.ts +256 -0
  40. package/src/tool/tebasCheck/i18n/sv.ts +256 -0
  41. package/src/tool/tebasCheck/i18n/tr.ts +256 -0
  42. package/src/tool/tebasCheck/i18n/zh.ts +256 -0
  43. package/src/tool/tebasCheck/index.ts +12 -0
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import type { ToolLocaleContent } from '../types';
4
+
5
+ const sharingLocales = ['ja', 'ko', 'zh'];
6
+
7
+ interface ValidateParams {
8
+ toolId: string;
9
+ locale: string;
10
+ content: ToolLocaleContent;
11
+ enSlug: string;
12
+ slugs: Map<string, string>;
13
+ }
14
+
15
+ const validateLocaleSlug = ({
16
+ toolId,
17
+ locale,
18
+ content,
19
+ enSlug,
20
+ slugs,
21
+ }: ValidateParams) => {
22
+ expect(
23
+ content.slug,
24
+ `Tool "${toolId}" locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
25
+ ).toMatch(/^[a-z0-9-]+$/);
26
+
27
+ if (locale === 'en') {
28
+ return;
29
+ }
30
+
31
+ if (sharingLocales.includes(locale)) {
32
+ expect(
33
+ content.slug,
34
+ `Tool "${toolId}" locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
35
+ ).toBe(enSlug);
36
+ } else {
37
+ expect(
38
+ content.slug,
39
+ `Tool "${toolId}" locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propia idioma`,
40
+ ).not.toBe(enSlug);
41
+
42
+ if (slugs.has(content.slug)) {
43
+ const previousLocale = slugs.get(content.slug);
44
+ expect(
45
+ false,
46
+ `Tool "${toolId}" locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propia idioma`,
47
+ ).toBe(true);
48
+ }
49
+ slugs.set(content.slug, locale);
50
+ }
51
+ };
52
+
53
+ describe('Slug Localization and Uniqueness Validation', () => {
54
+ ALL_TOOLS.forEach((tool) => {
55
+ describe(`Tool: ${tool.entry.id}`, () => {
56
+ it('every locale should have a unique, translated slug', async () => {
57
+ const slugs = new Map<string, string>();
58
+ const locales = Object.keys(tool.entry.i18n);
59
+
60
+ let enSlug = '';
61
+ if (locales.includes('en')) {
62
+ const enLoader = tool.entry.i18n['en' as keyof typeof tool.entry.i18n];
63
+ const enContent = (await enLoader?.()) as ToolLocaleContent;
64
+ enSlug = enContent.slug;
65
+ }
66
+
67
+ for (const locale of locales) {
68
+ const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
69
+ const content = (await loader?.()) as ToolLocaleContent;
70
+ validateLocaleSlug({
71
+ toolId: tool.entry.id,
72
+ locale,
73
+ content,
74
+ enSlug,
75
+ slugs,
76
+ });
77
+ }
78
+ });
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,256 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { SorteoUI } from '../ui';
4
+
5
+ const slug = 'zufallsgenerator';
6
+ const title = 'Zufallsgenerator für Namen (Streaming)';
7
+ const description =
8
+ 'Wählen Sie per Zufall einen Gewinner aus einer Namensliste aus. Kostenloses, schnelles und visuelles Gewinnspiel-Tool für Twitch, YouTube und Events.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Ist dieses Gewinnspiel wirklich zufällig?',
13
+ answer:
14
+ 'Ja, wir verwenden den kryptografischen Zufallsalgorithmus des Browsers (Web Crypto API), um sicherzustellen, dass jeder Teilnehmer genau die gleiche Gewinnwahrscheinlichkeit hat – ohne Verzerrung oder Manipulation.',
15
+ },
16
+ {
17
+ question: 'Kann ich diesen Generator auf Twitch oder YouTube verwenden?',
18
+ answer:
19
+ 'Absolut. Da es sich um ein Web-Tool handelt, können Sie das Fenster mit OBS erfassen oder Ihren Bildschirm direkt teilen. Das klare Design und die Animationen sind so konzipiert, dass das Publikum den Prozess mit totaler Transparenz mitverfolgen kann.',
20
+ },
21
+ {
22
+ question: 'Wie kann ich verhindern, dass jemand doppelt teilnimmt?',
23
+ answer:
24
+ 'Das Tool verfügt über eine automatische Funktion zur Bereinigung von Dubletten, die identische Namen oder Namen mit geringfügigen Abweichungen in der Leerzeichensetzung erkennt, damit jede reale Person nur einmal gezählt wird.',
25
+ },
26
+ {
27
+ question: 'Kann ich mehrere Gewinner gleichzeitig ziehen?',
28
+ answer:
29
+ 'Ja, Sie können die Anzahl der gewünschten Gewinner konfigurieren, bevor Sie auf die Schaltfläche klicken. Das Tool listet die Glücklichen übersichtlich auf, damit Sie sie in Ihrem Livestream nennen können.',
30
+ },
31
+ {
32
+ question: 'Wie viele Namen kann ich zur Liste hinzufügen?',
33
+ answer:
34
+ 'Es gibt keine strikte Grenze vonseiten des Tools. Wir haben die Engine so optimiert, dass sie Listen mit tausenden Teilnehmern ohne Performance-Probleme verarbeitet – ideal also auch für riesige Gewinnspiele.',
35
+ },
36
+ {
37
+ question: 'Werden meine Daten oder die Teilnehmerliste gespeichert?',
38
+ answer:
39
+ 'Nein, niemals. Ihre Privatsphäre steht an erster Stelle. Der gesamte Prozess läuft lokal in Ihrem Webbrowser ab. Die eingegebenen Namen werden niemals an unsere Server gesendet oder in einer Datenbank gespeichert.',
40
+ },
41
+ ];
42
+
43
+ const howToData = [
44
+ {
45
+ name: 'Teilnehmerliste vorbereiten',
46
+ text: 'Kopieren Sie die Namensliste aus Ihrem Chat, Excel oder sozialen Netzwerk und fügen Sie sie in das Textfeld ein.',
47
+ },
48
+ {
49
+ name: 'Optionen konfigurieren',
50
+ text: 'Wählen Sie aus, wie viele Gewinner Sie benötigen und ob Sie Duplikate oder leere Zeilen filtern möchten.',
51
+ },
52
+ {
53
+ name: 'Die „Glücksfee“ starten',
54
+ text: 'Klicken Sie auf die Schaltfläche für die Auslosung. Eine visuelle Animation sorgt für Spannung, bevor der Gewinner enthüllt wird.',
55
+ },
56
+ {
57
+ name: 'Ergebnisse verkünden',
58
+ text: 'Kopieren Sie die Namen der Gewinner, um sie in Ihren sozialen Netzwerken oder im Streaming-Chat zu teilen.',
59
+ },
60
+ ];
61
+
62
+ const faqSchema: WithContext<FAQPage> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'FAQPage',
65
+ mainEntity: faqData.map((item) => ({
66
+ '@type': 'Question',
67
+ name: item.question,
68
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
69
+ })),
70
+ };
71
+
72
+ const howToSchema: WithContext<HowTo> = {
73
+ '@context': 'https://schema.org',
74
+ '@type': 'HowTo',
75
+ name: title,
76
+ description,
77
+ step: howToData.map((step, i) => ({
78
+ '@type': 'HowToStep',
79
+ position: i + 1,
80
+ name: step.name,
81
+ text: step.text,
82
+ })),
83
+ };
84
+
85
+ const appSchema: WithContext<SoftwareApplication> = {
86
+ '@context': 'https://schema.org',
87
+ '@type': 'SoftwareApplication',
88
+ name: title,
89
+ description,
90
+ applicationCategory: 'UtilityApplication',
91
+ operatingSystem: 'All',
92
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
93
+ inLanguage: 'de',
94
+ };
95
+
96
+ export const content: ToolLocaleContent<SorteoUI> = {
97
+ slug,
98
+ title,
99
+ description,
100
+ faqTitle: 'Häufig gestellte Fragen',
101
+ faq: faqData,
102
+ bibliographyTitle: 'Technische Referenzen',
103
+ bibliography: [
104
+ {
105
+ name: 'Web Crypto API: getRandomValues()',
106
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues',
107
+ },
108
+ {
109
+ name: 'Fisher-Yates Shuffle Algorithmus',
110
+ url: 'https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle',
111
+ },
112
+ ],
113
+ howTo: howToData,
114
+ schemas: [faqSchema as any, howToSchema as any, appSchema as any],
115
+ seo: [
116
+ {
117
+ type: 'title',
118
+ text: 'Zufallsgenerator für Namen und Teilnehmerlisten online',
119
+ level: 2,
120
+ },
121
+ {
122
+ type: 'paragraph',
123
+ html: 'Fragen Sie sich, wie Sie online ein Gewinnspiel schnell, sicher und absolut transparent durchführen können? Unser kostenloser <strong>Namens-Picker</strong> ist die ultimative Lösung, um in Sekunden einen Zufallsgewinner zu ziehen. Entwickelt, um einfach, visuell und effektiv zu sein – perfekt für jedes Szenario, in dem Sie eine digitale „Glücksfee“ benötigen.',
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: 'Egal, ob Sie ein Gewinnspiel in sozialen Netzwerken verwalten, eine große Verlosung auf Ihrem Streaming-Kanal durchführen oder einfach nur entscheiden, wer heute den Müll rausbringt: Unser Zufalls-Selektor garantiert dank moderner kryptografischer Algorithmen absolute Unparteilichkeit. <strong>Keine Manipulation, keine Bevorzugung, nur reiner Zufall.</strong>'
128
+ },
129
+ {
130
+ type: 'title',
131
+ text: 'Anwendungsfälle',
132
+ level: 3,
133
+ },
134
+ {
135
+ type: 'comparative',
136
+ columns: 2,
137
+ items: [
138
+ {
139
+ title: 'Gewinnspiele in sozialen Medien',
140
+ description: 'Ideal für Instagram-, Twitter (X)- oder Facebook-Wettbewerbe. Kopieren Sie einfach die Namen aus den Kommentaren und fügen Sie sie ein. Das Tool entfernt automatisch Duplikate.',
141
+ },
142
+ {
143
+ title: 'Twitch / YouTube Streaming',
144
+ description: 'Dank unseres Studio-Modus mit flüssigen Animationen und integrierten Sounds können Sie Ihren Bildschirm direkt in OBS teilen und Ihrem Publikum eine spannende Show bieten, während Sie die Gewinner live ziehen.',
145
+ },
146
+ {
147
+ title: 'Klassen und Team Dynamiken',
148
+ description: 'Lehrer und Teamleiter können das Tool nutzen, um Zufallsgruppen zu bilden, die Reihenfolge von Präsentationen festzulegen oder Aufgaben völlig transparent und ohne Bevorzugung zu verteilen.',
149
+ },
150
+ {
151
+ title: 'Wichteln und Events',
152
+ description: 'Vereinfachen Sie die Organisation von Familienfeiern, Büro-Verlosungen oder Wichtel-Runden, indem Sie Namen sofort zufällig ziehen, ohne Zettelwirtschaft oder komplizierte Logistik.',
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ type: 'title',
158
+ text: 'Warum ist unser Tool anders?',
159
+ level: 3,
160
+ },
161
+ {
162
+ type: 'list',
163
+ items: [
164
+ '<strong>Echte Kryptografie:</strong> Wir verwenden die Web Crypto API des Browsers (W3C-Standard) anstelle schwacher Pseudo-Zufallsgeneratoren. Jede Auslosung ist mathematisch unparteiisch.',
165
+ '<strong>Keine Speicherung:</strong> Ihre Daten verlassen niemals Ihren Browser. Wir verkaufen keine Listen, erstellen keine Profile und speichern nichts. Alles wird lokal verarbeitet.',
166
+ '<strong>Visuelles Design:</strong> Der Kino-Modus und die Animationen machen jedes Gewinnspiel zu einem Erlebnis. Ideal für die Erfassung mit OBS oder für Live-Streams.',
167
+ '<strong>Umgang mit Duplikaten:</strong> Erkennt automatisch wiederholte Namen oder Varianten (zusätzliche Leerzeichen, Groß-/Kleinschreibung usw.), um sicherzustellen, dass jede reale Person nur einmal zählt.',
168
+ ],
169
+ },
170
+ {
171
+ type: 'title',
172
+ text: 'So nutzen Sie das Gewinnspiel Schritt für Schritt',
173
+ level: 3,
174
+ },
175
+ {
176
+ type: 'list',
177
+ items: [
178
+ '<strong>Schritt 1 – Teilnehmer eingeben:</strong> Fügen Sie Ihre Namensliste in das Haupttextfeld ein. Das Tool erkennt jeden Zeilenumbruch als separaten Teilnehmer. Haben Sie Duplikate? Kein Problem, das Tool entfernt sie automatisch.',
179
+ '<strong>Schritt 2 – Anpassen:</strong> In den Einstellungen können Sie den Countdown für mehr Spannung, den Konfetti-Effekt zum Feiern oder die „Blacklist“ zum Ausschluss bestimmter Namen aktivieren.',
180
+ '<strong>Schritt 3 – Auslosen!</strong> Klicken Sie auf die Hauptschaltfläche. Unsere Engine generiert eine kryptografisch sichere Zufallsauswahl. Die Gewinner werden klar und einprägsam angezeigt.',
181
+ ],
182
+ },
183
+ {
184
+ type: 'title',
185
+ text: 'Gewichtete Einträge: Geben Sie bestimmten Teilnehmern einen Vorteil',
186
+ level: 3,
187
+ },
188
+ {
189
+ type: 'paragraph',
190
+ html: 'Möchten Sie Ihre treuesten Abonnenten belohnen oder bestimmten Teilnehmern mehr Chancen einräumen? Unser System für <strong>gewichtete Einträge</strong> ist einzigartig und ermöglicht es Ihnen, jedem Namen ein „Gewicht“ oder einen Multiplikator zuzuweisen, ohne ihn mehrfach aufschreiben zu müssen.',
191
+ },
192
+ {
193
+ type: 'tip',
194
+ title: 'So weisen Sie Gewichte zu',
195
+ html: '<p>Verwenden Sie ein Sternchen (*) oder ein „x“, gefolgt von der Anzahl der Teilnahmen. Beispiele:</p><ul><li><strong>„Max * 5“</strong> – Max nimmt teil, als wäre er 5 Personen</li><li><strong>„Julia x 10“</strong> – Julia hat eine 10-mal höhere Gewinnchance</li><li><strong>„Peter“</strong> – Kein Symbol = 1 normale Teilnahme</li></ul><p>Dies ist perfekt für Gewinnspiele, bei denen Sie VIP-Abonnenten oder speziellen Nutzern einen Vorteil gewähren möchten.</p>',
196
+ },
197
+ {
198
+ type: 'title',
199
+ text: 'Vollständige Privatsphäre und Sicherheit',
200
+ level: 3,
201
+ },
202
+ {
203
+ type: 'paragraph',
204
+ html: 'Im Gegensatz zu anderen Online-Tools <strong>speichern wir Ihre Daten nicht</strong>. Die gesamte Verarbeitung der Namen und die Durchführung der Auslosung erfolgen lokal in Ihrem eigenen Browser. Ihre Teilnehmerlisten werden niemals über das Netzwerk übertragen oder auf einem externen Server gespeichert.',
205
+ },
206
+ {
207
+ type: 'paragraph',
208
+ html: '<strong>Was bedeutet das?</strong> Ihre Teilnehmerliste gehört Ihnen und nur Ihnen. Sobald Sie den Tab schließen, ist sie weg. Keine Tracking-Cookies, keine Nutzerprofile, keine zentrale Datenbank. Garantierte Privatsphäre für Sie und Ihre Teilnehmer.',
209
+ },
210
+ {
211
+ type: 'title',
212
+ text: 'Mathematische Transparenz',
213
+ level: 3,
214
+ },
215
+ {
216
+ type: 'paragraph',
217
+ html: 'Manchen mag die Frage kommen: „Was, wenn die Ergebnisse manipuliert werden?“ Die Antwort ist einfach: <strong>Wir können es nicht.</strong> Der Code der Auslosung ist deterministisch und kryptografisch. Keine versteckten Variablen, keine „geschobenen“ Ergebnisse.',
218
+ },
219
+ {
220
+ type: 'paragraph',
221
+ html: 'Jeder Gewinner ist das direkte Ergebnis des Fisher-Yates-Shuffle-Algorithmus, der auf Ihre exakte Liste angewendet wird und echte kryptografische Entropie nutzt. Wenn Sie den Prozess prüfen möchten: Der Code ist auf GitHub öffentlich einsehbar.',
222
+ },
223
+ ],
224
+ ui: {
225
+ faqTitle: 'Häufig gestellte Fragen',
226
+ bibliographyTitle: 'Technische Referenzen',
227
+ title: 'Zufalls Gewinnspiel',
228
+ totalParticipants: 'Teilnehmer (einmalig)',
229
+ ready: 'BEREIT',
230
+ participants: 'Teilnehmer',
231
+ settings: 'Einstellungen',
232
+ importFile: 'Datei importieren',
233
+ clearAll: 'Alle löschen',
234
+ placeholder: 'Namen hier eingeben oder einfügen...\nMax Mustermann\nErika Musterfrau\n@twitch_user',
235
+ onePerLine: '1 Teilnehmer pro Zeile',
236
+ lines: 'Zeilen',
237
+ filters: 'Filter',
238
+ allowDuplicates: 'Duplikate zulassen',
239
+ winnerCount: 'Anzahl der Gewinner',
240
+ autoRemove: 'Gewinner automatisch entfernen',
241
+ blacklist: 'Blacklist (Ausschließen)',
242
+ blacklistPlaceholder: 'Gesperrte Namen (einer pro Zeile)...',
243
+ blacklistInfo: 'Diese Nutzer nehmen nicht am Gewinnspiel teil.',
244
+ sceneEffects: 'Szeneneffekte',
245
+ countdown: 'Countdown (3s)',
246
+ confetti: 'Sieges-Konfetti',
247
+ zenMode: 'Zen-Modus (Panel ausblenden)',
248
+ waitingParticipants: 'Warten auf Teilnehmer...',
249
+ winner: 'GEWINNER',
250
+ reroll: 'Erneut auslosen',
251
+ history: 'Verlauf dieser Sitzung',
252
+ noWinnersYet: 'Noch keine Gewinner...',
253
+ startGiveaway: 'Gewinnspiel starten',
254
+ studioHeader: 'LIVE STUDIO v2.0',
255
+ },
256
+ };
@@ -0,0 +1,256 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { SorteoUI } from '../ui';
4
+
5
+ const slug = 'pengundi-nama-acak';
6
+ const title = 'Pengundi Nama Acak untuk Streaming';
7
+ const description =
8
+ 'Pilih pemenang secara acak dari daftar nama. Alat giveaway gratis, cepat, dan visual untuk Twitch, YouTube, dan acara lainnya.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Apakah pengundian ini benar-benar acak?',
13
+ answer:
14
+ 'Ya, kami menggunakan algoritma keacakan kriptografi browser (Web Crypto API) untuk memastikan bahwa setiap peserta memiliki probabilitas menang yang sama persis, tanpa bias atau manipulasi.',
15
+ },
16
+ {
17
+ question: 'Bisakah saya menggunakan pengundian ini di Twitch atau YouTube?',
18
+ answer:
19
+ 'Tentu saja. Sebagai alat berbasis web, Anda dapat menangkap jendela dengan OBS atau membagikan layar Anda secara langsung. Desain bersih dan animasinya dirancang agar penonton dapat melihat prosesnya dengan transparansi total.',
20
+ },
21
+ {
22
+ question: 'Bagaimana cara mencegah seseorang berpartisipasi dua kali?',
23
+ answer:
24
+ 'Alat ini memiliki fungsi "pembersihan duplikat" otomatis yang mendeteksi nama yang identik atau nama dengan variasi spasi kecil untuk memastikan setiap orang nyata hanya dihitung satu kali.',
25
+ },
26
+ {
27
+ question: 'Bisakah saya memilih beberapa pemenang sekaligus?',
28
+ answer:
29
+ 'Ya, Anda dapat mengonfigurasi jumlah pemenang yang diinginkan sebelum mengklik tombol. Alat ini akan mencantumkan pemenang dengan jelas sehingga Anda dapat menyebutkan nama mereka dalam siaran langsung Anda.',
30
+ },
31
+ {
32
+ question: 'Berapa banyak nama yang bisa saya tambahkan ke daftar?',
33
+ answer:
34
+ 'Tidak ada batasan ketat yang diberlakukan oleh alat ini. Kami telah mengoptimalkan mesin untuk menangani daftar ribuan peserta tanpa masalah performa, menjadikannya ideal bahkan untuk giveaway besar.',
35
+ },
36
+ {
37
+ question: 'Apakah data saya atau daftar peserta disimpan?',
38
+ answer:
39
+ 'Tidak, tidak pernah. Privasi Anda adalah prioritas utama. Seluruh proses pengundian berjalan secara lokal di browser web Anda. Nama-nama yang Anda masukkan tidak pernah dikirim ke server kami atau disimpan dalam database apa pun.',
40
+ },
41
+ ];
42
+
43
+ const howToData = [
44
+ {
45
+ name: 'Siapkan daftar peserta',
46
+ text: 'Salin daftar nama dari chat Anda, Excel, atau media sosial dan tempelkan ke dalam kotak teks.',
47
+ },
48
+ {
49
+ name: 'Konfigurasi opsi giveaway',
50
+ text: 'Pilih berapa banyak pemenang yang Anda butuhkan dan apakah Anda ingin memfilter duplikat atau nama kosong.',
51
+ },
52
+ {
53
+ name: 'Luncurkan "tangan jujur"',
54
+ text: 'Klik tombol undi. Animasi visual akan menjaga ketegangan sebelum mengungkapkan pemenangnya.',
55
+ },
56
+ {
57
+ name: 'Umumkan hasil',
58
+ text: 'Salin nama pemenang untuk dibagikan di media sosial atau chat streaming Anda.',
59
+ },
60
+ ];
61
+
62
+ const faqSchema: WithContext<FAQPage> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'FAQPage',
65
+ mainEntity: faqData.map((item) => ({
66
+ '@type': 'Question',
67
+ name: item.question,
68
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
69
+ })),
70
+ };
71
+
72
+ const howToSchema: WithContext<HowTo> = {
73
+ '@context': 'https://schema.org',
74
+ '@type': 'HowTo',
75
+ name: title,
76
+ description,
77
+ step: howToData.map((step, i) => ({
78
+ '@type': 'HowToStep',
79
+ position: i + 1,
80
+ name: step.name,
81
+ text: step.text,
82
+ })),
83
+ };
84
+
85
+ const appSchema: WithContext<SoftwareApplication> = {
86
+ '@context': 'https://schema.org',
87
+ '@type': 'SoftwareApplication',
88
+ name: title,
89
+ description,
90
+ applicationCategory: 'UtilityApplication',
91
+ operatingSystem: 'All',
92
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
93
+ inLanguage: 'id',
94
+ };
95
+
96
+ export const content: ToolLocaleContent<SorteoUI> = {
97
+ slug,
98
+ title,
99
+ description,
100
+ faqTitle: 'Pertanyaan yang Sering Diajukan',
101
+ faq: faqData,
102
+ bibliographyTitle: 'Referensi Teknis',
103
+ bibliography: [
104
+ {
105
+ name: 'Web Crypto API: getRandomValues()',
106
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues',
107
+ },
108
+ {
109
+ name: 'Algoritma Fisher-Yates Shuffle',
110
+ url: 'https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle',
111
+ },
112
+ ],
113
+ howTo: howToData,
114
+ schemas: [faqSchema as any, howToSchema as any, appSchema as any],
115
+ seo: [
116
+ {
117
+ type: 'title',
118
+ text: 'Pengundi Nama Acak dan Daftar Peserta Online',
119
+ level: 2,
120
+ },
121
+ {
122
+ type: 'paragraph',
123
+ html: 'Bingung bagaimana cara melakukan giveaway online secara cepat, aman, dan transparan total? Alat <strong>Pengundi Nama</strong> gratis kami adalah solusi terbaik untuk memilih pemenang secara acak dalam hitungan detik. Didesain agar sederhana, visual, dan efektif, alat ini sempurna untuk skenario apa pun di mana Anda membutuhkan "tangan jujur" digital.',
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: 'Baik Anda mengelola kontes di media sosial, giveaway besar di saluran streaming Anda, atau sekadar memutuskan siapa yang membuang sampah hari ini, pemilih acak kami menjamin ketidakberpihakan total berkat algoritma kriptografi modern. <strong>Tanpa manipulasi, tanpa bias, hanya keacakan murni.</strong>'
128
+ },
129
+ {
130
+ type: 'title',
131
+ text: 'Contoh Penggunaan',
132
+ level: 3,
133
+ },
134
+ {
135
+ type: 'comparative',
136
+ columns: 2,
137
+ items: [
138
+ {
139
+ title: 'Giveaway Media Sosial',
140
+ description: 'Ideal untuk kontes Instagram, Twitter (X), atau Facebook. Cukup salin nama dari komentar dan tempelkan untuk memilih pemenang yang adil. Alat ini secara otomatis menghapus duplikat.',
141
+ },
142
+ {
143
+ title: 'Streaming Twitch / YouTube',
144
+ description: 'Berkat Mode Studio kami dengan animasi halus dan suara yang terintegrasi, Anda dapat membagikan layar Anda secara langsung di OBS dan memberikan pertunjukan visual yang menarik bagi penonton sambil memilih pemenang secara langsung.',
145
+ },
146
+ {
147
+ title: 'Dinamika Kelas dan Tim',
148
+ description: 'Guru dan pemimpin tim dapat menggunakannya untuk membentuk kelompok acak, memilih siapa yang presentasi duluan, atau membagi tugas secara acak dengan transparansi total dan tanpa pilih kasih.',
149
+ },
150
+ {
151
+ title: 'Tukar Kado (Secret Santa) dan Acara',
152
+ description: 'Sederhanakan pengorganisiran acara keluarga, giveaway kantor, atau Tukar Kado dengan memilih nama secara acak instan tanpa perlu kertas atau logistik yang rumit.',
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ type: 'title',
158
+ text: 'Mengapa alat kami berbeda?',
159
+ level: 3,
160
+ },
161
+ {
162
+ type: 'list',
163
+ items: [
164
+ '<strong>Kriptografi Nyata:</strong> Kami menggunakan Web Crypto API browser (standar W3C) alih-alih generator acak semu yang lemah. Setiap pengundian tidak memihak secara matematis.',
165
+ '<strong>Tanpa Penyimpanan:</strong> Data Anda tidak pernah meninggalkan browser Anda. Kami tidak menjual daftar, kami tidak memprofilkan Anda, kami tidak menyimpan apa pun. Pemrosesan murni secara lokal.',
166
+ '<strong>Desain Visual:</strong> Mode Cinema dan animasi menjadikan setiap giveaway sebagai acara yang tak terlupakan. Sempurna untuk tangkapan OBS atau siaran langsung.',
167
+ '<strong>Penanganan Duplikat:</strong> Secara otomatis mendeteksi nama yang berulang atau varian (spasi tambahan, huruf besar-kecil, dll.) untuk memastikan setiap orang nyata hanya dihitung satu kali.',
168
+ ],
169
+ },
170
+ {
171
+ type: 'title',
172
+ text: 'Cara menggunakan giveaway langkah demi langkah',
173
+ level: 3,
174
+ },
175
+ {
176
+ type: 'list',
177
+ items: [
178
+ '<strong>Langkah 1 - Masukkan peserta:</strong> Tempelkan daftar nama Anda ke dalam kotak teks utama. Alat ini secara otomatis mendeteksi setiap ganti baris sebagai peserta yang berbeda. Ada duplikat? Tidak masalah, alat ini akan menghapusnya.',
179
+ '<strong>Langkah 2 - Sesuaikan:</strong> Di tab pengaturan Anda dapat mengaktifkan hitung mundur untuk menciptakan ketegangan, efek konfeti untuk merayakan, atau mengaktifkan "daftar hitam" untuk mengecualikan nama tertentu.',
180
+ '<strong>Langkah 3 - Undi!</strong> Klik tombol utama dan mesin kami akan menghasilkan pilihan acak yang aman secara kriptografi. Pemenang akan ditampilkan dengan jelas dan berkesan.',
181
+ ],
182
+ },
183
+ {
184
+ type: 'title',
185
+ text: 'Entri Berbobot: Berikan Keuntungan kepada Peserta Tertentu',
186
+ level: 3,
187
+ },
188
+ {
189
+ type: 'paragraph',
190
+ html: 'Ingin memberi penghargaan kepada subscriber setia Anda atau memberi lebih banyak peluang kepada peserta tertentu? Sistem <strong>Entri Berbobot</strong> kami unik dan memungkinkan Anda untuk memberikan "bobot" atau pengganda pada nama mana pun tanpa harus menulisnya berkali-kali.',
191
+ },
192
+ {
193
+ type: 'tip',
194
+ title: 'Cara memberikan bobot pada nama',
195
+ html: '<p>Gunakan tanda bintang (*) atau "x" diikuti dengan jumlah partisipasi. Contoh:</p><ul><li><strong>"Budi * 5"</strong> - Budi berkompetisi seolah-olah dia adalah 5 orang</li><li><strong>"Siti x 10"</strong> - Siti memiliki peluang 10 kali lebih besar</li><li><strong>"Joko"</strong> - Tanpa simbol = 1 entri reguler</li></ul><p>Ini sempurna untuk giveaway di mana Anda ingin memberikan keuntungan kepada subscriber VIP atau pengguna spesial.</p>',
196
+ },
197
+ {
198
+ type: 'title',
199
+ text: 'Privasi dan Keamanan Total',
200
+ level: 3,
201
+ },
202
+ {
203
+ type: 'paragraph',
204
+ html: 'Berbeda dengan alat online lainnya, <strong>kami tidak menyimpan data Anda</strong>. Semua pemrosesan nama dan eksekusi pengundian terjadi secara lokal di browser Anda sendiri. Daftar peserta Anda tidak pernah dikirim melalui jaringan atau disimpan di server eksternal mana pun.',
205
+ },
206
+ {
207
+ type: 'paragraph',
208
+ html: '<strong>Apa artinya ini?</strong> Daftar peserta Anda adalah milik Anda sepenuhnya. Begitu tab ditutup, data akan hilang. Tanpa cookie pelacakan, tanpa profil pengguna, tanpa basis data terpusat. Jaminan privasi maksimal untuk Anda dan mereka yang berpartisipasi dalam giveaway Anda.',
209
+ },
210
+ {
211
+ type: 'title',
212
+ text: 'Transparansi Matematis',
213
+ level: 3,
214
+ },
215
+ {
216
+ type: 'paragraph',
217
+ html: 'Beberapa orang mungkin bertanya: "Bagaimana jika Anda memanipulasi hasilnya?" Jawabannya sederhana: <strong>kami tidak bisa</strong>. Kode pengundian bersifat deterministik dan kriptografis. Tanpa variabel tersembunyi, tanpa pengaturan di belakang layar.',
218
+ },
219
+ {
220
+ type: 'paragraph',
221
+ html: 'Setiap pemenang adalah hasil langsung dari algoritma Fisher-Yates Shuffle yang diterapkan pada daftar Anda, menggunakan entropi kriptografi nyata. Jika Anda ingin mengaudit prosesnya, kode tersedia di GitHub untuk pemeriksaan publik.',
222
+ },
223
+ ],
224
+ ui: {
225
+ faqTitle: 'Pertanyaan yang Sering Diajukan',
226
+ bibliographyTitle: 'Referensi Teknis',
227
+ title: 'Giveaway Acak',
228
+ totalParticipants: 'Total Peserta Unik',
229
+ ready: 'SIAP',
230
+ participants: 'Peserta',
231
+ settings: 'Pengaturan',
232
+ importFile: 'Impor File',
233
+ clearAll: 'Hapus semua',
234
+ placeholder: 'Ketik atau tempel nama di sini...\nBudi Santoso\nSiti Aminah\n@user_twitch',
235
+ onePerLine: '1 peserta per baris',
236
+ lines: 'baris',
237
+ filters: 'Filter',
238
+ allowDuplicates: 'Izinkan Duplikat',
239
+ winnerCount: 'Jumlah Pemenang',
240
+ autoRemove: 'Hapus Pemenang Otomatis',
241
+ blacklist: 'Daftar Hitam (Pengecualian)',
242
+ blacklistPlaceholder: 'Nama yang dilarang (satu per baris)...',
243
+ blacklistInfo: 'Pengguna ini tidak akan masuk dalam pengundian.',
244
+ sceneEffects: 'Efek Adegan',
245
+ countdown: 'Hitung Mundur (3d)',
246
+ confetti: 'Konfeti Kemenangan',
247
+ zenMode: 'Mode Zen (Sembunyikan Panel)',
248
+ waitingParticipants: 'Menunggu peserta...',
249
+ winner: 'PEMENANG',
250
+ reroll: 'Undi Ulang',
251
+ history: 'Riwayat sesi ini',
252
+ noWinnersYet: 'Belum ada pemenang...',
253
+ startGiveaway: 'Mulai Giveaway',
254
+ studioHeader: 'LIVE STUDIO v2.0',
255
+ },
256
+ };