-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathlang.php
More file actions
227 lines (206 loc) · 10.8 KB
/
Copy pathlang.php
File metadata and controls
227 lines (206 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
<?php
/**
* SayNoMore - lang.php
* Created by Leproide - https://github.com/Leproide/SayNoMore
*
* Distributed under GNU GPL v2.
* No warranty provided.
*
* Sistema di traduzione minimale basato su Accept-Language.
* - Se il browser preferisce italiano (it, it-IT, ecc) -> italiano
* - Altrimenti -> inglese (lingua di default)
*
* Uso:
* require_once __DIR__ . '/lang.php';
* echo t('btn.generate');
* echo t('error.too_large', ['size' => 64]);
*
* Per aggiungere nuove stringhe basta aggiungerle in entrambi gli array.
*/
/**
* Determina la lingua attiva basandosi su Accept-Language.
* Cached: la calcola una volta sola per richiesta.
*/
function snm_lang(): string {
static $cached = null;
if ($cached !== null) return $cached;
$accept = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
if (!is_string($accept) || $accept === '') {
return $cached = 'en';
}
// Parsing leggero: per ogni voce "lang;q=0.x" estraggo lang e qualita'.
// Non uso intl/Locale per non aggiungere dipendenze. Robusto abbastanza
// per il caso d'uso (it vs en).
$entries = explode(',', $accept);
$best = ['lang' => 'en', 'q' => -1.0];
foreach ($entries as $entry) {
$parts = explode(';', trim($entry));
$lang = strtolower(trim($parts[0] ?? ''));
if ($lang === '') continue;
// Solo il codice principale (es. "it-IT" -> "it")
$primary = explode('-', $lang)[0];
$q = 1.0;
for ($i = 1; $i < count($parts); $i++) {
$kv = explode('=', trim($parts[$i]), 2);
if (count($kv) === 2 && strtolower(trim($kv[0])) === 'q') {
$q = (float)trim($kv[1]);
}
}
if ($q > $best['q']) {
$best = ['lang' => $primary, 'q' => $q];
}
}
// Per ora supportiamo solo italiano e inglese (default).
return $cached = ($best['lang'] === 'it') ? 'it' : 'en';
}
/**
* Restituisce la stringa tradotta per la chiave fornita.
* Supporta interpolazione semplice di placeholder {nome}.
*
* @param string $key Chiave (es. 'btn.generate')
* @param array $vars Variabili per l'interpolazione (es. ['size' => 64])
*/
function t(string $key, array $vars = []): string {
static $strings = null;
if ($strings === null) {
$strings = snm_translations();
}
$lang = snm_lang();
$msg = $strings[$lang][$key] ?? $strings['en'][$key] ?? $key;
if (!empty($vars)) {
foreach ($vars as $k => $v) {
$msg = str_replace('{' . $k . '}', (string)$v, $msg);
}
}
return $msg;
}
/**
* Tabella delle traduzioni.
* Centralizzata qui per renderle facili da aggiornare.
*/
function snm_translations(): array {
return [
'it' => [
// Titoli pagina
'page.title.index' => 'SayNoMore',
'page.title.view' => 'SayNoMore - Visualizza',
'page.title.unlock' => 'Sblocca Segreto',
'page.title.error' => 'SayNoMore - Errore',
// Form index
'index.placeholder.secret' => 'Inserisci il tuo segreto...',
'index.required_field' => 'Compila questo campo.',
'index.placeholder.password' => 'Inserisci una password',
'index.label.expiry' => 'Scadenza del link:',
'index.opt.1day' => '1 giorno',
'index.opt.3days' => '3 giorni',
'index.opt.7days' => '7 giorni (default)',
'index.opt.14days' => '14 giorni',
'index.opt.30days' => '30 giorni (massimo)',
'index.btn.generate' => 'Genera link',
'index.label.notify_email' => 'Email per notifiche',
'index.placeholder.notify_email' => 'tua@email.it',
'index.label.notify_cb' => 'Inviami una mail quando il segreto viene letto o distrutto',
'index.maildebug_warning' => '⚠ Debug email attivo: ogni invio viene registrato in maildebug.txt. Disattivalo in produzione.',
'index.link.label' => 'Link generato (copia e invia):',
'index.btn.copy' => 'Copia',
'index.btn.copy.success' => 'Copiato!',
'index.btn.copy.error' => 'Errore',
'index.btn.new' => 'Crea un altro segreto',
// Form view
'view.heading.unlock' => 'Sblocca Segreto',
'view.heading.secret' => 'Il tuo segreto:',
'view.placeholder.password' => 'Password di visione',
'view.btn.unlock' => 'Sblocca segreto',
'view.btn.retry' => 'Riprova',
'view.btn.home' => 'Home',
'view.btn.copy_secret' => 'Copia negli appunti',
'view.btn.copy_secret.success'=> 'Copiato!',
'view.btn.copy_secret.error' => 'Errore',
'view.noscript' => 'Questo servizio richiede JavaScript abilitato per recuperare la chiave dal link.',
'view.noscript.short' => 'Questo servizio richiede JavaScript abilitato.',
// Errori
'err.heading' => 'Errore',
'err.input_invalid' => 'Input non valido.',
'err.too_large' => 'Segreto troppo grande. Massimo {size} KB.',
'err.gen_internal' => 'Errore interno durante la generazione.',
'err.entropy' => 'Sorgente di entropia non disponibile.',
'err.encryption' => 'Errore interno durante la cifratura.',
'err.save' => 'Errore interno durante il salvataggio.',
'err.token_invalid' => 'Token non valido.',
'err.key_invalid' => 'Chiave non valida o mancante.',
'err.link_invalid' => 'Link non valido o già usato.',
'err.busy' => 'Richiesta in corso, riprova.',
'err.too_many' => 'Troppi tentativi falliti. Il segreto è stato distrutto.',
'err.wrong_pass' => 'Password errata.',
'err.notify_email_invalid' => 'Email di notifica non valida.',
'err.password_required' => 'La password è obbligatoria.',
'err.decrypt_failed' => 'Decifratura fallita: il link potrebbe essere corrotto o incompleto.',
'err.crypto_unavailable' => 'Contesto sicuro richiesto: apri questa pagina via HTTPS o tramite un indirizzo .onion per abilitare la cifratura nel browser.',
'index.js_required' => 'JavaScript è necessario per creare un segreto: la cifratura avviene nel tuo browser.',
// Footer
'footer.tagline' => 'Made with 💀 by Leprechaun',
'footer.github' => 'GitHub',
],
'en' => [
// Page titles
'page.title.index' => 'SayNoMore',
'page.title.view' => 'SayNoMore - View',
'page.title.unlock' => 'Unlock Secret',
'page.title.error' => 'SayNoMore - Error',
// Index form
'index.placeholder.secret' => 'Enter your secret...',
'index.required_field' => 'Please fill out this field.',
'index.placeholder.password' => 'Enter a password',
'index.label.expiry' => 'Link expiry:',
'index.opt.1day' => '1 day',
'index.opt.3days' => '3 days',
'index.opt.7days' => '7 days (default)',
'index.opt.14days' => '14 days',
'index.opt.30days' => '30 days (maximum)',
'index.btn.generate' => 'Generate link',
'index.label.notify_email' => 'Notification email',
'index.placeholder.notify_email' => 'you@email.com',
'index.label.notify_cb' => 'Email me when the secret is read or destroyed',
'index.maildebug_warning' => '⚠ Mail debug enabled: every send is logged to maildebug.txt. Turn it off in production.',
'index.link.label' => 'Link generated (copy and send):',
'index.btn.copy' => 'Copy',
'index.btn.copy.success' => 'Copied!',
'index.btn.copy.error' => 'Error',
'index.btn.new' => 'Create another secret',
// View form
'view.heading.unlock' => 'Unlock Secret',
'view.heading.secret' => 'Your secret:',
'view.placeholder.password' => 'View password',
'view.btn.unlock' => 'Unlock secret',
'view.btn.retry' => 'Retry',
'view.btn.home' => 'Home',
'view.btn.copy_secret' => 'Copy to clipboard',
'view.btn.copy_secret.success'=> 'Copied!',
'view.btn.copy_secret.error' => 'Error',
'view.noscript' => 'This service requires JavaScript enabled to retrieve the key from the link.',
'view.noscript.short' => 'This service requires JavaScript enabled.',
// Errors
'err.heading' => 'Error',
'err.input_invalid' => 'Invalid input.',
'err.too_large' => 'Secret too large. Maximum {size} KB.',
'err.gen_internal' => 'Internal error during generation.',
'err.entropy' => 'Entropy source not available.',
'err.encryption' => 'Internal error during encryption.',
'err.save' => 'Internal error during saving.',
'err.token_invalid' => 'Invalid token.',
'err.key_invalid' => 'Invalid or missing key.',
'err.link_invalid' => 'Invalid or already-used link.',
'err.busy' => 'Request in progress, please retry.',
'err.too_many' => 'Too many failed attempts. The secret has been destroyed.',
'err.wrong_pass' => 'Wrong password.',
'err.notify_email_invalid' => 'Invalid notification email.',
'err.password_required' => 'Password is required.',
'err.decrypt_failed' => 'Decryption failed: the link may be corrupted or incomplete.',
'err.crypto_unavailable' => 'Secure context required: open this page over HTTPS or via a .onion address to enable in-browser encryption.',
'index.js_required' => 'JavaScript is required to create a secret: encryption happens in your browser.',
// Footer
'footer.tagline' => 'Made with 💀 by Leprechaun',
'footer.github' => 'GitHub',
],
];
}