Skip to content

Commit dd90bf3

Browse files
committed
feat(i18n): internacionalización completa ES/EN — v2.2.0
- i18n.ts: ~130 claves EN/ES (app, vault, share, detail, card, settings, shortcuts) - App.svelte: títulos de pestaña y botón de tema traducidos - VaultScreen: bulk actions (Fusionar hilo / Eliminar) - PostCard: aria-labels, autor desconocido, nota, confirmación - PostDetailScreen: galería, carrusel, vídeo inline, errores de script - SettingsScreen: todas las secciones, modal importar, atajos - CategoryManager: 'Sin nombre' → t() - Auto-detección navigator.language, persistencia localStorage - Versión bump 2.1.0 → 2.2.0
1 parent 2007c1a commit dd90bf3

8 files changed

Lines changed: 226 additions & 104 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "threadsvault-desktop",
33
"private": true,
4-
"version": "2.1.0",
4+
"version": "2.2.0",
55
"description": "Bóveda local para posts de Threads. Guarda contenido offline sin nube ni telemetría.",
66
"type": "module",
77
"scripts": {

src/App.svelte

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import { onMount } from 'svelte'
33
import { loadVault, posts } from './lib/stores/vault'
44
import type { Post } from './lib/types'
5+
import { t, locale } from './lib/i18n'
6+
$locale; // reactive subscription — re-renders on locale change
57
68
// ── Zoom global ──────────────────────────────────────────
79
const ZOOM_STEP = 0.1
@@ -31,9 +33,9 @@
3133
3234
function computeTitle(route: string, allPosts: Post[]): string {
3335
if (route === '#/' || route === '') return 'ThreadsVault'
34-
if (route === '#/share') return 'ThreadsVault — Añadir post'
35-
if (route === '#/settings') return 'ThreadsVault — Ajustes'
36-
if (route === '#/categories') return 'ThreadsVault — Categorías'
36+
if (route === '#/share') return t('app.page_share')
37+
if (route === '#/settings') return t('app.page_settings')
38+
if (route === '#/categories') return t('app.page_categories')
3739
if (route.startsWith('#/post/')) {
3840
const id = route.replace('#/post/', '')
3941
const post = allPosts.find(p => p.id === id)
@@ -43,6 +45,7 @@
4345
}
4446
4547
$effect(() => {
48+
void $locale // track locale changes → re-run when language switches
4649
const title = computeTitle(currentRoute, $posts)
4750
document.title = title
4851
if ('__TAURI_INTERNALS__' in window) {
@@ -140,8 +143,8 @@
140143
<button
141144
class="theme-toggle"
142145
onclick={() => { theme = theme === 'dark' ? 'light' : 'dark' }}
143-
title={theme === 'dark' ? 'Cambiar a tema claro' : 'Cambiar a tema oscuro'}
144-
aria-label="Alternar tema"
146+
title={theme === 'dark' ? t('app.theme_to_light') : t('app.theme_to_dark')}
147+
aria-label={t('app.theme_toggle')}
145148
>
146149
{#if theme === 'dark'}
147150
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">

src/components/CategoryManager.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@
131131
132132
function getCategoryLabel(cat: Category): string {
133133
const label = cat.name?.trim()
134-
return label ? label : 'Sin nombre'
134+
return label ? label : t('vault.no_category_name')
135135
}
136136
</script>
137137

src/components/PostCard.svelte

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import { getPostDisplayPath } from "../lib/utils/url-parser";
55
import { savePost, loadVault } from "../lib/stores/vault";
66
import { invoke } from "@tauri-apps/api/core";
7+
import { t, locale } from "../lib/i18n";
8+
$locale; // reactive subscription
79
810
let {
911
post,
@@ -161,7 +163,7 @@
161163
<!-- svelte-ignore a11y_no_noninteractive_element_to_interactive_role -->
162164
<article
163165
role="button"
164-
aria-label="Abrir detalle del post"
166+
aria-label={t('card.open')}
165167
class="rounded-2xl p-4 mb-3 cursor-pointer group relative overflow-hidden"
166168
style="
167169
background: {selected ? 'rgba(124,77,255,0.12)' : 'var(--vault-card-bg)'};
@@ -238,7 +240,7 @@
238240
letter-spacing: 0.015em;
239241
"
240242
>
241-
{post.author || "Autor desconocido"}
243+
{post.author || t('card.unknown_author')}
242244
</p>
243245

244246
<!--
@@ -329,7 +331,7 @@
329331
el.style.background = "rgba(124,77,255,0.14)";
330332
el.style.borderColor = "rgba(124,77,255,0.30)";
331333
}}
332-
aria-label="Abrir enlace externo"
334+
aria-label={t('card.open_link')}
333335
>
334336
<svg
335337
width="9"
@@ -394,7 +396,7 @@
394396
el.style.borderRightColor = "transparent";
395397
el.style.borderBottomColor = "transparent";
396398
}}
397-
aria-label="Editar nota personal"
399+
aria-label={t('card.edit_note')}
398400
>
399401
<!-- Pencil icon with glow dot -->
400402
<span
@@ -599,15 +601,15 @@
599601
onclick={(e) => {
600602
e.stopPropagation();
601603
onDelete(post.id);
602-
}}></button
604+
}}>{t('common.yes')}</button
603605
>
604606
<button
605607
class="px-2.5 py-1 rounded-lg text-xs font-semibold"
606608
style="background: var(--vault-card-hover-bg); color: var(--vault-on-bg); font-family: var(--font-display)"
607609
onclick={(e) => {
608610
e.stopPropagation();
609611
confirmingDelete = false;
610-
}}>No</button
612+
}}>{t('common.no')}</button
611613
>
612614
</div>
613615
{:else}
@@ -624,7 +626,7 @@
624626
e.stopPropagation();
625627
confirmingDelete = true;
626628
}}
627-
aria-label="Eliminar post"
629+
aria-label={t('card.delete_post')}
628630
>
629631
<svg
630632
width="9"
@@ -720,7 +722,7 @@
720722
<textarea
721723
bind:value={noteValue}
722724
rows="3"
723-
placeholder="Escribe tu nota…"
725+
placeholder={t('card.note_placeholder')}
724726
class="w-full bg-transparent resize-none text-sm leading-relaxed outline-none px-3 py-2"
725727
style="
726728
color: var(--vault-on-bg);
@@ -751,7 +753,7 @@
751753
border: 1px solid rgba(0,188,212,0.45);
752754
font-family: var(--font-display);
753755
opacity: {savingNote ? '0.6' : '1'};
754-
">{savingNote ? "Guardando…" : "Guardar"}</button
756+
">{savingNote ? t('common.saving') : t('common.save')}</button
755757
>
756758

757759
<button
@@ -766,7 +768,7 @@
766768
border: 1px solid var(--vault-ghost-btn-border);
767769
color: var(--vault-on-bg-muted);
768770
font-family: var(--font-display);
769-
">Cancelar</button
771+
">{t('common.cancel')}</button
770772
>
771773

772774
{#if post.note}
@@ -781,7 +783,7 @@
781783
border: 1px solid var(--vault-danger-border);
782784
color: var(--vault-danger-color);
783785
font-family: var(--font-display);
784-
">Eliminar nota</button
786+
">{t('card.delete_note')}</button
785787
>
786788
{/if}
787789
</div>

src/lib/i18n.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ const translations = {
9292
'detail.update_media': 'Actualizar media',
9393
'detail.personal_note': 'NOTA PERSONAL',
9494
'detail.unknown_author': 'Autor desconocido',
95+
'detail.downloading': 'Procesando descarga...',
9596

9697
// PostCard
9798
'card.open': 'Abrir detalle del post',
@@ -140,6 +141,64 @@ const translations = {
140141
'settings.shortcuts': 'Atajos de teclado',
141142
'settings.dev_title': 'Desarrollador de ThreadsVault',
142143
'settings.close': 'CERRAR',
144+
'settings.import_completed': 'Importación completada',
145+
'settings.import_error_title': 'Error al importar',
146+
'settings.import_error_fallback': 'No se pudo completar la importación. Verifica que el archivo sea un backup válido de ThreadsVault.',
147+
'settings.import_skipped_note': 'Los elementos omitidos tenían datos incompatibles. El resto se importó correctamente.',
148+
'settings.import_warning_suffix': 'y los reemplazará con los del archivo seleccionado. Esta acción no se puede deshacer.',
149+
'settings.error_read_file': 'No se pudo leer el archivo seleccionado.',
150+
'settings.error_import_unknown': 'Error desconocido al importar',
151+
152+
// App
153+
'app.page_share': 'ThreadsVault — Añadir post',
154+
'app.page_settings': 'ThreadsVault — Ajustes',
155+
'app.page_categories': 'ThreadsVault — Categorías',
156+
'app.theme_to_light': 'Cambiar a tema claro',
157+
'app.theme_to_dark': 'Cambiar a tema oscuro',
158+
'app.theme_toggle': 'Alternar tema',
159+
160+
// VaultScreen bulk
161+
'vault.merging': 'Fusionando...',
162+
'vault.merge_thread': 'Fusionar hilo',
163+
'vault.deleting': 'Eliminando...',
164+
165+
// PostDetailScreen galería / vídeo
166+
'detail.hide_failed': 'Ocultar fallidos',
167+
'detail.show_failed': 'Mostrar fallidos',
168+
'detail.image_of': 'Imagen {i} de {total}',
169+
'detail.video_loading': 'Cargando vídeo…',
170+
'detail.video_on_threads': 'Vídeo en Threads',
171+
'detail.video_searching': 'Buscando fuente reproducible…',
172+
'detail.video_protected': 'Threads protege sus vídeos. Ábrelo directamente para reproducirlo.',
173+
'detail.video_downloading': 'Descargando...',
174+
'detail.download_video': 'Descargar video',
175+
'detail.resource_unavailable': 'Recurso no visible en app. Puedes abrir o descargar desde la URL.',
176+
'detail.lightbox_alt': 'Vista ampliada',
177+
178+
// PostDetailScreen mensajes de error (script)
179+
'detail.error_download': 'No se pudo descargar el video.',
180+
'detail.error_no_new_text': 'No se encontró texto nuevo. Abre el post en Threads e intenta de nuevo.',
181+
'detail.error_extract': 'Error al extraer. Comprueba la conexión e inténtalo de nuevo.',
182+
'detail.error_stream': 'No se pudo resolver el stream del vídeo.',
183+
'detail.error_stream_app': 'Falló la resolución del vídeo en la app.',
184+
'detail.preparing_download': 'Preparando descarga...',
185+
'detail.error_timeout': 'La descarga tardó demasiado. Reintenta o abre en Threads.',
186+
'detail.error_download_failed': 'La descarga falló.',
187+
'detail.error_no_new_media': 'No se encontró contenido nuevo. Abre el post en Threads e intenta de nuevo.',
188+
'detail.error_update_media': 'No se pudo actualizar media ahora. Abre el post en Threads y vuelve a intentar.',
189+
190+
// Shortcuts
191+
'shortcut.back': 'Volver atrás',
192+
'shortcut.add_post': 'Añadir post',
193+
'shortcut.search_posts': 'Buscar posts',
194+
'shortcut.search_app': 'Buscar en la app',
195+
'shortcut.navigate': 'Navegar entre posts',
196+
'shortcut.zoom_in': 'Acercar (zoom in)',
197+
'shortcut.zoom_out': 'Alejar (zoom out)',
198+
'shortcut.zoom_reset': 'Zoom normal',
199+
'shortcut.ctx_global': 'Global',
200+
'shortcut.ctx_vault': 'En vault',
201+
'shortcut.ctx_post': 'En post',
143202
},
144203
en: {
145204
// Common
@@ -232,6 +291,7 @@ const translations = {
232291
'detail.update_media': 'Update media',
233292
'detail.personal_note': 'PERSONAL NOTE',
234293
'detail.unknown_author': 'Unknown author',
294+
'detail.downloading': 'Processing download...',
235295

236296
// PostCard
237297
'card.open': 'Open post detail',
@@ -280,6 +340,64 @@ const translations = {
280340
'settings.shortcuts': 'Keyboard shortcuts',
281341
'settings.dev_title': 'ThreadsVault Developer',
282342
'settings.close': 'CLOSE',
343+
'settings.import_completed': 'Import completed',
344+
'settings.import_error_title': 'Import error',
345+
'settings.import_error_fallback': 'Could not complete import. Check that the file is a valid ThreadsVault backup.',
346+
'settings.import_skipped_note': 'Skipped items had incompatible data. The rest was imported correctly.',
347+
'settings.import_warning_suffix': 'and replace them with those from the selected file. This action cannot be undone.',
348+
'settings.error_read_file': 'Could not read the selected file.',
349+
'settings.error_import_unknown': 'Unknown import error',
350+
351+
// App
352+
'app.page_share': 'ThreadsVault — Add post',
353+
'app.page_settings': 'ThreadsVault — Settings',
354+
'app.page_categories': 'ThreadsVault — Categories',
355+
'app.theme_to_light': 'Switch to light theme',
356+
'app.theme_to_dark': 'Switch to dark theme',
357+
'app.theme_toggle': 'Toggle theme',
358+
359+
// VaultScreen bulk
360+
'vault.merging': 'Merging...',
361+
'vault.merge_thread': 'Merge thread',
362+
'vault.deleting': 'Deleting...',
363+
364+
// PostDetailScreen gallery / video
365+
'detail.hide_failed': 'Hide failed',
366+
'detail.show_failed': 'Show failed',
367+
'detail.image_of': 'Image {i} of {total}',
368+
'detail.video_loading': 'Loading video…',
369+
'detail.video_on_threads': 'Video on Threads',
370+
'detail.video_searching': 'Looking for playable source…',
371+
'detail.video_protected': 'Threads protects its videos. Open it directly to play it.',
372+
'detail.video_downloading': 'Downloading...',
373+
'detail.download_video': 'Download video',
374+
'detail.resource_unavailable': 'Resource not visible in app. You can open or download from the URL.',
375+
'detail.lightbox_alt': 'Enlarged view',
376+
377+
// PostDetailScreen error messages (script)
378+
'detail.error_download': 'Could not download the video.',
379+
'detail.error_no_new_text': 'No new text found. Open the post on Threads and try again.',
380+
'detail.error_extract': 'Extraction error. Check your connection and try again.',
381+
'detail.error_stream': 'Could not resolve the video stream.',
382+
'detail.error_stream_app': 'Video stream resolution failed in app.',
383+
'detail.preparing_download': 'Preparing download...',
384+
'detail.error_timeout': 'Download took too long. Retry or open in Threads.',
385+
'detail.error_download_failed': 'Download failed.',
386+
'detail.error_no_new_media': 'No new content found. Open the post on Threads and try again.',
387+
'detail.error_update_media': 'Could not update media now. Open the post on Threads and try again.',
388+
389+
// Shortcuts
390+
'shortcut.back': 'Go back',
391+
'shortcut.add_post': 'Add post',
392+
'shortcut.search_posts': 'Search posts',
393+
'shortcut.search_app': 'Search in app',
394+
'shortcut.navigate': 'Navigate between posts',
395+
'shortcut.zoom_in': 'Zoom in',
396+
'shortcut.zoom_out': 'Zoom out',
397+
'shortcut.zoom_reset': 'Normal zoom',
398+
'shortcut.ctx_global': 'Global',
399+
'shortcut.ctx_vault': 'In vault',
400+
'shortcut.ctx_post': 'In post',
283401
}
284402
} as const
285403

0 commit comments

Comments
 (0)