github-license-scanner 0.1.0__py3-none-any.whl

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.
gls/i18n.py ADDED
@@ -0,0 +1,530 @@
1
+ """
2
+ Bilingual UI strings (Spanish / English) for the NiceGUI interface.
3
+
4
+ Usage:
5
+ from .i18n import t, normalize_lang
6
+ label = t("scan_button", "es")
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ DEFAULT_LANG = "es"
14
+ SUPPORTED = ("es", "en")
15
+
16
+ # All product-facing UI strings. Keys are stable English identifiers.
17
+ TRANSLATIONS: dict[str, dict[str, str]] = {
18
+ # Header
19
+ "app_title": {
20
+ "es": "GitHub License Scanner",
21
+ "en": "GitHub License Scanner",
22
+ },
23
+ "app_subtitle": {
24
+ "es": (
25
+ "Analiza la licencia de un repositorio y de sus dependencias para estimar "
26
+ "si puedes vender la app como software cerrado o si el copyleft obliga a abrir código."
27
+ ),
28
+ "en": (
29
+ "Analyze a repository license and its dependencies to estimate whether you can "
30
+ "sell the app as closed-source software or if copyleft requires opening the code."
31
+ ),
32
+ },
33
+ "app_features": {
34
+ "es": "Incluye historial, modo batch, aviso de copyright y recomendaciones de deploy.",
35
+ "en": "Includes history, batch mode, copyright notice, and deploy recommendations.",
36
+ },
37
+ "disclaimer": {
38
+ "es": (
39
+ "Aviso legal: esta herramienta ofrece heurísticas automatizadas y NO constituye "
40
+ "asesoramiento jurídico ni una opinión de compatibilidad de licencias. "
41
+ "No garantiza no infracción. Las decisiones de cumplimiento (copyleft, SaaS/AGPL, "
42
+ "atribución, contratos) deben revisarse con un abogado cualificado. "
43
+ "El historial se guarda en el servidor/local de esta instancia."
44
+ ),
45
+ "en": (
46
+ "Legal notice: this tool provides automated heuristics and is NOT legal advice "
47
+ "or a license-compatibility opinion. It does not warrant non-infringement. "
48
+ "Compliance decisions (copyleft, SaaS/AGPL, attribution, contracts) should be "
49
+ "reviewed with a qualified attorney. Scan history is stored on this instance."
50
+ ),
51
+ },
52
+ "token_hint": {
53
+ "es": "Opcional: define la variable de entorno GITHUB_TOKEN para evitar límites de la API de GitHub.",
54
+ "en": "Optional: set the GITHUB_TOKEN environment variable to avoid GitHub API rate limits.",
55
+ },
56
+ "language": {
57
+ "es": "Idioma",
58
+ "en": "Language",
59
+ },
60
+ "theme": {
61
+ "es": "Tema",
62
+ "en": "Theme",
63
+ },
64
+ "theme_dark": {
65
+ "es": "Oscuro",
66
+ "en": "Dark",
67
+ },
68
+ "theme_light": {
69
+ "es": "Claro",
70
+ "en": "Light",
71
+ },
72
+ "theme_toggle_to_light": {
73
+ "es": "Cambiar a modo claro",
74
+ "en": "Switch to light mode",
75
+ },
76
+ "theme_toggle_to_dark": {
77
+ "es": "Cambiar a modo oscuro",
78
+ "en": "Switch to dark mode",
79
+ },
80
+ "hero_badge": {
81
+ "es": "Análisis de licencias · Copyleft · Deploy",
82
+ "en": "License analysis · Copyleft · Deploy",
83
+ },
84
+ "scan_card_title": {
85
+ "es": "Analizar repositorio",
86
+ "en": "Analyze repository",
87
+ },
88
+ "scan_card_help": {
89
+ "es": "Pega la URL de GitHub y obtén un veredicto de venta cerrada, riesgos por dependencia y dónde desplegar.",
90
+ "en": "Paste a GitHub URL to get a closed-sale verdict, per-dependency risks, and deploy tips.",
91
+ },
92
+ "feature_scan": {
93
+ "es": "Licencias del repo y paquetes",
94
+ "en": "Repo & package licenses",
95
+ },
96
+ "feature_copyleft": {
97
+ "es": "Señales GPL / AGPL",
98
+ "en": "GPL / AGPL signals",
99
+ },
100
+ "feature_deploy": {
101
+ "es": "Consejos de deploy",
102
+ "en": "Deploy advice",
103
+ },
104
+ "feature_batch": {
105
+ "es": "Modo lote e historial",
106
+ "en": "Batch mode & history",
107
+ },
108
+ "try_example": {
109
+ "es": "Probar ejemplo",
110
+ "en": "Try an example",
111
+ },
112
+ "empty_title": {
113
+ "es": "Aún no hay resultados",
114
+ "en": "No results yet",
115
+ },
116
+ "empty_body": {
117
+ "es": "Pega una URL de GitHub a la izquierda y pulsa Analizar. Verás el veredicto de venta cerrada, riesgos por paquete y recomendaciones de deploy.",
118
+ "en": "Paste a GitHub URL on the left and click Analyze. You’ll see the closed-sale verdict, package risks, and deploy recommendations.",
119
+ },
120
+ "step_fetch": {
121
+ "es": "Repositorio",
122
+ "en": "Repository",
123
+ },
124
+ "step_deps": {
125
+ "es": "Dependencias",
126
+ "en": "Dependencies",
127
+ },
128
+ "step_licenses": {
129
+ "es": "Licencias",
130
+ "en": "Licenses",
131
+ },
132
+ "step_verdict": {
133
+ "es": "Veredicto",
134
+ "en": "Verdict",
135
+ },
136
+ "workspace_title": {
137
+ "es": "Espacio de trabajo",
138
+ "en": "Workspace",
139
+ },
140
+ "results_live": {
141
+ "es": "Resultados en vivo",
142
+ "en": "Live results",
143
+ },
144
+ "scroll_results": {
145
+ "es": "Ver resultados",
146
+ "en": "View results",
147
+ },
148
+ "open_repo": {
149
+ "es": "Abrir en GitHub",
150
+ "en": "Open on GitHub",
151
+ },
152
+ "risk_legend": {
153
+ "es": "Agrupado por licencia · revisa el badge de riesgo en cada paquete",
154
+ "en": "Grouped by license · check each package risk badge",
155
+ },
156
+ "how_it_works": {
157
+ "es": "Cómo funciona",
158
+ "en": "How it works",
159
+ },
160
+ "how_1": {
161
+ "es": "1. Leemos la licencia del repo en GitHub",
162
+ "en": "1. We read the repo license from GitHub",
163
+ },
164
+ "how_2": {
165
+ "es": "2. Parseamos package.json, requirements, Cargo…",
166
+ "en": "2. We parse package.json, requirements, Cargo…",
167
+ },
168
+ "how_3": {
169
+ "es": "3. Consultamos registros (npm, PyPI, crates…)",
170
+ "en": "3. We query registries (npm, PyPI, crates…)",
171
+ },
172
+ "how_4": {
173
+ "es": "4. Calculamos riesgos y sugerimos deploy",
174
+ "en": "4. We score risk and suggest deploy targets",
175
+ },
176
+ "recent_scans": {
177
+ "es": "Escaneos recientes",
178
+ "en": "Recent scans",
179
+ },
180
+ "rescan": {
181
+ "es": "Reanalizar",
182
+ "en": "Rescan",
183
+ },
184
+ # Tabs
185
+ "tab_scan": {"es": "Escanear", "en": "Scan"},
186
+ "tab_batch": {"es": "Batch", "en": "Batch"},
187
+ "tab_history": {"es": "Historial", "en": "History"},
188
+ # Scan form
189
+ "url_label": {
190
+ "es": "URL del repositorio GitHub",
191
+ "en": "GitHub repository URL",
192
+ },
193
+ "url_placeholder": {
194
+ "es": "https://github.com/owner/repo",
195
+ "en": "https://github.com/owner/repo",
196
+ },
197
+ "scan_button": {"es": "Analizar", "en": "Analyze"},
198
+ "scanning_status": {
199
+ "es": "Analizando repositorio, dependencias y registros…",
200
+ "en": "Analyzing repository, dependencies, and registries…",
201
+ },
202
+ "notify_paste_url": {
203
+ "es": "Pega una URL de GitHub",
204
+ "en": "Paste a GitHub URL",
205
+ },
206
+ "notify_strong_copyleft": {
207
+ "es": "Copyleft fuerte detectado",
208
+ "en": "Strong copyleft detected",
209
+ },
210
+ "notify_done": {
211
+ "es": "Análisis completado",
212
+ "en": "Analysis complete",
213
+ },
214
+ "notify_unexpected": {
215
+ "es": "Error inesperado: {error}",
216
+ "en": "Unexpected error: {error}",
217
+ },
218
+ # Batch
219
+ "batch_help": {
220
+ "es": "Pega varias URLs (una por línea). Se analizarán en secuencia.",
221
+ "en": "Paste several URLs (one per line). They will be analyzed in sequence.",
222
+ },
223
+ "batch_urls_label": {"es": "URLs", "en": "URLs"},
224
+ "batch_button": {"es": "Analizar lote", "en": "Analyze batch"},
225
+ "batch_status": {
226
+ "es": "Analizando {i}/{total}: {url}",
227
+ "en": "Analyzing {i}/{total}: {url}",
228
+ },
229
+ "notify_batch_empty": {
230
+ "es": "Añade al menos una URL",
231
+ "en": "Add at least one URL",
232
+ },
233
+ "notify_batch_done": {
234
+ "es": "Lote completado: {count} repos",
235
+ "en": "Batch complete: {count} repos",
236
+ },
237
+ "notify_batch_error": {
238
+ "es": "Error en batch: {error}",
239
+ "en": "Batch error: {error}",
240
+ },
241
+ "notify_batch_too_many": {
242
+ "es": "Lote limitado a {max} URLs (había {count}). Se procesarán las primeras.",
243
+ "en": "Batch capped at {max} URLs (got {count}). Processing the first ones.",
244
+ },
245
+ "rate_limited": {
246
+ "es": (
247
+ "Límite de análisis alcanzado ({limit} por {window}s). "
248
+ "Espera ~{wait}s o reduce el uso. Esto protege la API de GitHub."
249
+ ),
250
+ "en": (
251
+ "Scan rate limit reached ({limit} per {window}s). "
252
+ "Wait ~{wait}s or reduce usage. This protects the GitHub API."
253
+ ),
254
+ },
255
+ # History
256
+ "history_refresh": {
257
+ "es": "Actualizar historial",
258
+ "en": "Refresh history",
259
+ },
260
+ "history_clear": {
261
+ "es": "Borrar historial",
262
+ "en": "Clear history",
263
+ },
264
+ "history_cleared": {
265
+ "es": "Historial borrado.",
266
+ "en": "History cleared.",
267
+ },
268
+ "history_privacy_note": {
269
+ "es": (
270
+ "Privacidad: con autenticación activada el historial es por usuario "
271
+ "(history/<usuario>.json en el directorio de datos del usuario). Sin auth, el archivo es compartido en la instancia. "
272
+ "Puedes borrarlo aquí."
273
+ ),
274
+ "en": (
275
+ "Privacy: with auth enabled, history is per user (history/<user>.json in the user data directory). "
276
+ "Without auth, the file is shared on the instance. You can clear it here."
277
+ ),
278
+ },
279
+ "history_empty": {
280
+ "es": "Aún no hay historial. Analiza un repositorio para empezar.",
281
+ "en": "No history yet. Analyze a repository to get started.",
282
+ },
283
+ "history_count": {
284
+ "es": "{count} repositorio(s) en historial",
285
+ "en": "{count} repository(ies) in history",
286
+ },
287
+ "link_privacy": {"es": "Privacidad", "en": "Privacy"},
288
+ "link_terms": {"es": "Términos", "en": "Terms"},
289
+ "link_legal": {"es": "Aviso legal", "en": "Legal notice"},
290
+ "back_home": {"es": "← Volver al escáner", "en": "← Back to scanner"},
291
+ "col_when": {"es": "Fecha", "en": "Date"},
292
+ "col_repo": {"es": "Repositorio", "en": "Repository"},
293
+ "col_license": {"es": "Licencia", "en": "License"},
294
+ "col_pkgs": {"es": "Paquetes", "en": "Packages"},
295
+ "col_closed": {"es": "Venta cerrada", "en": "Closed sale"},
296
+ "col_force": {"es": "Obliga abrir", "en": "Forces open"},
297
+ "yes": {"es": "Sí", "en": "Yes"},
298
+ "no": {"es": "No", "en": "No"},
299
+ # Results section
300
+ "results": {"es": "Resultados", "en": "Results"},
301
+ "batch_results": {
302
+ "es": "Resultados del lote",
303
+ "en": "Batch results",
304
+ },
305
+ "batch_last_detail": {
306
+ "es": "Detalle del último escaneo del lote",
307
+ "en": "Detail of the last scan in the batch",
308
+ },
309
+ # Verdict
310
+ "verdict_bad_title": {
311
+ "es": "Alto riesgo para software cerrado (heurística)",
312
+ "en": "High risk for closed-source distribution (heuristic)",
313
+ },
314
+ "verdict_bad_sub": {
315
+ "es": (
316
+ "Señales de copyleft fuerte (GPL/AGPL/SSPL u otras). El resultado depende del "
317
+ "modelo de enlace, distribución y uso en red. No es una prohibición legal automática."
318
+ ),
319
+ "en": (
320
+ "Strong copyleft signals (GPL/AGPL/SSPL or similar). Outcome depends on linking, "
321
+ "distribution, and network use. This is not an automatic legal prohibition."
322
+ ),
323
+ },
324
+ "verdict_warn_title": {
325
+ "es": "Posible venta cerrada — con precauciones",
326
+ "en": "Closed-source sale may be possible — with caveats",
327
+ },
328
+ "verdict_warn_sub": {
329
+ "es": "No hay copyleft fuerte detectado, pero hay licencias débiles o desconocidas que requieren revisión.",
330
+ "en": "No strong copyleft detected, but weak or unknown licenses need manual review.",
331
+ },
332
+ "verdict_ok_title": {
333
+ "es": "Parece viable vender como software cerrado",
334
+ "en": "Closed-source sale appears viable",
335
+ },
336
+ "verdict_ok_sub": {
337
+ "es": "No se detectó copyleft fuerte en la licencia del repo ni en las dependencias resueltas.",
338
+ "en": "No strong copyleft found in the repo license or resolved dependencies.",
339
+ },
340
+ "verdict_incomplete_title": {
341
+ "es": "Análisis incompleto",
342
+ "en": "Incomplete analysis",
343
+ },
344
+ "verdict_incomplete_sub": {
345
+ "es": "No se pudo obtener suficiente información de GitHub (rate limit, red o permisos). Configura GITHUB_TOKEN e inténtalo de nuevo.",
346
+ "en": "Could not fetch enough data from GitHub (rate limit, network, or permissions). Set GITHUB_TOKEN and try again.",
347
+ },
348
+ # Meta cards
349
+ "meta_repo_license": {
350
+ "es": "Licencia del repositorio",
351
+ "en": "Repository license",
352
+ },
353
+ "meta_language": {
354
+ "es": "Lenguaje principal",
355
+ "en": "Primary language",
356
+ },
357
+ "meta_packages": {
358
+ "es": "Paquetes analizados",
359
+ "en": "Packages analyzed",
360
+ },
361
+ "meta_forces_open": {
362
+ "es": "¿Obliga a abrir código?",
363
+ "en": "Forces open source?",
364
+ },
365
+ "meta_risk_score": {
366
+ "es": "Score de riesgo",
367
+ "en": "Risk score",
368
+ },
369
+ "meta_prod_dev": {
370
+ "es": "Prod / Dev",
371
+ "en": "Prod / Dev",
372
+ },
373
+ "scope_prod": {"es": "prod", "en": "prod"},
374
+ "scope_dev": {"es": "dev", "en": "dev"},
375
+ "export_markdown": {
376
+ "es": "Exportar Markdown",
377
+ "en": "Export Markdown",
378
+ },
379
+ "export_copied": {
380
+ "es": "Reporte Markdown copiado al portapapeles",
381
+ "en": "Markdown report copied to clipboard",
382
+ },
383
+ "export_download": {
384
+ "es": "Descargar .md",
385
+ "en": "Download .md",
386
+ },
387
+ "export_sbom": {
388
+ "es": "Exportar SBOM",
389
+ "en": "Export SBOM",
390
+ },
391
+ "export_sbom_help": {
392
+ "es": (
393
+ "Bill of Materials generado a partir de dependencias declaradas y licencias "
394
+ "de registro (heurístico; no es un SBOM binario de build)."
395
+ ),
396
+ "en": (
397
+ "Bill of Materials from declared dependencies and registry licenses "
398
+ "(heuristic; not a full binary build SBOM)."
399
+ ),
400
+ },
401
+ "export_cyclonedx": {
402
+ "es": "Descargar CycloneDX JSON",
403
+ "en": "Download CycloneDX JSON",
404
+ },
405
+ "export_spdx": {
406
+ "es": "Descargar SPDX JSON",
407
+ "en": "Download SPDX JSON",
408
+ },
409
+ "login_title": {
410
+ "es": "Iniciar sesión",
411
+ "en": "Sign in",
412
+ },
413
+ "login_help": {
414
+ "es": "Esta instancia requiere autenticación. El historial es privado por usuario.",
415
+ "en": "This instance requires authentication. History is private per user.",
416
+ },
417
+ "login_user": {"es": "Usuario", "en": "Username"},
418
+ "login_pass": {"es": "Contraseña", "en": "Password"},
419
+ "login_button": {"es": "Entrar", "en": "Sign in"},
420
+ "login_ok": {"es": "Sesión iniciada", "en": "Signed in"},
421
+ "login_fail": {
422
+ "es": "Usuario o contraseña incorrectos",
423
+ "en": "Invalid username or password",
424
+ },
425
+ "logout": {"es": "Salir", "en": "Log out"},
426
+ "logged_in_as": {
427
+ "es": "Sesión: {user}",
428
+ "en": "Signed in: {user}",
429
+ },
430
+ "risk_label_minimal": {"es": "mínimo", "en": "minimal"},
431
+ "risk_label_low": {"es": "bajo", "en": "low"},
432
+ "risk_label_medium": {"es": "medio", "en": "medium"},
433
+ "risk_label_high": {"es": "alto", "en": "high"},
434
+ "unknown": {"es": "Desconocida", "en": "Unknown"},
435
+ # Risk labels
436
+ "risk_permissive": {"es": "Permisiva", "en": "Permissive"},
437
+ "risk_strong": {"es": "Copyleft fuerte", "en": "Strong copyleft"},
438
+ "risk_weak": {"es": "Copyleft débil", "en": "Weak copyleft"},
439
+ "risk_unknown": {"es": "Desconocida", "en": "Unknown"},
440
+ # Sections
441
+ "notes_warnings": {
442
+ "es": "Notas / advertencias",
443
+ "en": "Notes / warnings",
444
+ },
445
+ "deploy_title": {
446
+ "es": "Dónde desplegar (recomendación)",
447
+ "en": "Where to deploy (recommendation)",
448
+ },
449
+ "deploy_help": {
450
+ "es": "Sugerencias heurísticas según el stack detectado (lenguaje, dependencias, Docker).",
451
+ "en": "Heuristic suggestions based on the detected stack (language, dependencies, Docker).",
452
+ },
453
+ "deploy_docs": {"es": "Documentación", "en": "Documentation"},
454
+ "deploy_none": {
455
+ "es": "Sin recomendaciones.",
456
+ "en": "No recommendations.",
457
+ },
458
+ "copyright_title": {
459
+ "es": "Aviso de copyright",
460
+ "en": "Copyright notice",
461
+ },
462
+ "copyright_copy": {
463
+ "es": "Copiar aviso de copyright",
464
+ "en": "Copy copyright notice",
465
+ },
466
+ "copyright_copied": {
467
+ "es": "Aviso de copyright copiado al portapapeles",
468
+ "en": "Copyright notice copied to clipboard",
469
+ },
470
+ "copyright_copy_fail": {
471
+ "es": "No se pudo copiar automáticamente. Selecciona el texto del área y copia manualmente (Ctrl+C).",
472
+ "en": "Could not copy automatically. Select the text area and copy manually (Ctrl+C).",
473
+ },
474
+ "replacements_title": {
475
+ "es": "Sugerencias de reemplazo (paquetes problemáticos)",
476
+ "en": "Replacement suggestions (problematic packages)",
477
+ },
478
+ "replacements_help": {
479
+ "es": "Alternativas heurísticas permisivas. Verifica siempre la licencia real del sustituto.",
480
+ "en": "Heuristic permissive alternatives. Always verify the replacement's actual license.",
481
+ },
482
+ "packages_by_license": {
483
+ "es": "Paquetes agrupados por licencia",
484
+ "en": "Packages grouped by license",
485
+ },
486
+ "packages_none": {
487
+ "es": "No se encontraron (o no se pudieron resolver) dependencias.",
488
+ "en": "No dependencies found (or could not be resolved).",
489
+ },
490
+ "packages_count": {
491
+ "es": "{license} — {count} paquete(s)",
492
+ "en": "{license} — {count} package(s)",
493
+ },
494
+ "registry_link": {"es": "registro", "en": "registry"},
495
+ "dep_files": {
496
+ "es": "Archivos de dependencias analizados",
497
+ "en": "Dependency files analyzed",
498
+ },
499
+ "error_prefix": {"es": "Error: {error}", "en": "Error: {error}"},
500
+ }
501
+
502
+
503
+ def normalize_lang(lang: str | None) -> str:
504
+ """Return a supported language code (default Spanish)."""
505
+ if not lang:
506
+ return DEFAULT_LANG
507
+ code = lang.lower().strip()
508
+ if code.startswith("en"):
509
+ return "en"
510
+ if code.startswith("es"):
511
+ return "es"
512
+ return DEFAULT_LANG if code not in SUPPORTED else code
513
+
514
+
515
+ def t(key: str, lang: str = DEFAULT_LANG, **kwargs: Any) -> str:
516
+ """
517
+ Translate a UI key for the given language.
518
+
519
+ Optional kwargs are applied with str.format for placeholders.
520
+ Falls back to English, then to the key itself.
521
+ """
522
+ lang = normalize_lang(lang)
523
+ entry = TRANSLATIONS.get(key) or {}
524
+ text = entry.get(lang) or entry.get("en") or entry.get("es") or key
525
+ if kwargs:
526
+ try:
527
+ return text.format(**kwargs)
528
+ except (KeyError, ValueError):
529
+ return text
530
+ return text