Skip to content

Commit f5fc8a3

Browse files
committed
feat(i18n): complete translation coverage for all UI strings
- Add missing translation keys for: - Search: search_clear - Bookmarks: bookmarks_close, bookmarks_close_modal, bookmarks_note_placeholder, bookmarks_empty_current_chat - Transcription: transcription_hide_title, transcription_transcribe_title - Stats: stats_computing, stats_messages, stats_avg_day, stats_media, stats_duration - Context menu: context_menu_close - Language: language_change - Participants: participants_view_all - Replace all hardcoded aria-labels, titles, and placeholders with i18n calls - Update components: SearchBar, ChatList, BookmarksPanel, BookmarkModal, MessageBubble, ChatView, ChatStats, LocaleSwitcher, +page.svelte - Add Portuguese translations for all new keys
1 parent 95d27d9 commit f5fc8a3

11 files changed

Lines changed: 102 additions & 62 deletions

messages/en.json

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"search_result_of": "{current} of {total}",
3535
"search_previous": "Previous match",
3636
"search_next": "Next match",
37+
"search_clear": "Clear search",
3738

3839
"perspective_view_as": "View as",
3940
"perspective_select": "Select perspective",
@@ -51,19 +52,28 @@
5152
"bookmarks_delete": "Delete",
5253
"bookmarks_comment_placeholder": "Add a note (optional)...",
5354
"bookmarks_empty": "No bookmarks yet",
55+
"bookmarks_empty_current_chat": "No bookmarks in this chat",
5456
"bookmarks_empty_hint": "Click the bookmark icon on any message to save it here",
5557
"bookmarks_all_chats": "All chats",
5658
"bookmarks_current_chat": "Current chat",
5759
"bookmarks_export": "Export",
5860
"bookmarks_import": "Import",
5961
"bookmarks_import_success": "Imported {count} bookmarks",
6062
"bookmarks_from_chat": "From: {chat}",
63+
"bookmarks_close": "Close bookmarks panel",
64+
"bookmarks_close_modal": "Close",
65+
"bookmarks_note_placeholder": "Add a note to this bookmark...",
6166

62-
"stats_title": "Statistics",
67+
"stats_title": "Chat Statistics",
6368
"stats_view": "View statistics",
6469
"stats_close": "Close",
65-
"stats_total_messages": "Total Messages",
70+
"stats_computing": "Computing statistics...",
71+
"stats_messages": "Messages",
6672
"stats_participants": "Participants",
73+
"stats_avg_day": "Avg/Day",
74+
"stats_media": "Media",
75+
"stats_duration": "Duration",
76+
"stats_total_messages": "Total Messages",
6777
"stats_media_shared": "Media Shared",
6878
"stats_date_range": "Date Range",
6979
"stats_avg_per_day": "Avg. per day",
@@ -98,8 +108,10 @@
98108

99109
"transcription_title": "Transcription",
100110
"transcription_transcribe": "Transcribe audio",
111+
"transcription_transcribe_title": "Transcribe this voice message using AI",
101112
"transcription_show": "Show transcription",
102113
"transcription_hide": "Hide transcription",
114+
"transcription_hide_title": "Hide transcription",
103115
"transcription_loading_model": "Loading AI model... {progress}%",
104116
"transcription_transcribing": "Transcribing...",
105117
"transcription_retry": "Retry",
@@ -111,6 +123,9 @@
111123
"auto_load_media": "Auto-load Media",
112124

113125
"context_menu_remove": "Remove Chat",
126+
"context_menu_close": "Close context menu",
127+
128+
"participants_view_all": "Click to see all participants",
114129

115130
"export_instructions_title": "How to export your WhatsApp chat",
116131
"export_step_1": "Open WhatsApp on your phone",
@@ -142,5 +157,6 @@
142157
"language_chinese": "中文",
143158
"language_korean": "한국어",
144159
"language_russian": "Русский",
145-
"language_auto": "Auto-detect"
160+
"language_auto": "Auto-detect",
161+
"language_change": "Change language"
146162
}

messages/pt.json

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"search_result_of": "{current} de {total}",
3535
"search_previous": "Resultado anterior",
3636
"search_next": "Próximo resultado",
37+
"search_clear": "Limpar busca",
3738

3839
"perspective_view_as": "Ver como",
3940
"perspective_select": "Selecionar perspectiva",
@@ -51,19 +52,28 @@
5152
"bookmarks_delete": "Excluir",
5253
"bookmarks_comment_placeholder": "Adicione uma nota (opcional)...",
5354
"bookmarks_empty": "Nenhum marcador ainda",
55+
"bookmarks_empty_current_chat": "Nenhum marcador nesta conversa",
5456
"bookmarks_empty_hint": "Clique no ícone de marcador em qualquer mensagem para salvá-la aqui",
5557
"bookmarks_all_chats": "Todas as conversas",
5658
"bookmarks_current_chat": "Conversa atual",
5759
"bookmarks_export": "Exportar",
5860
"bookmarks_import": "Importar",
5961
"bookmarks_import_success": "{count} marcadores importados",
6062
"bookmarks_from_chat": "De: {chat}",
63+
"bookmarks_close": "Fechar painel de marcadores",
64+
"bookmarks_close_modal": "Fechar",
65+
"bookmarks_note_placeholder": "Adicione uma nota a este marcador...",
6166

62-
"stats_title": "Estatísticas",
67+
"stats_title": "Estatísticas da Conversa",
6368
"stats_view": "Ver estatísticas",
6469
"stats_close": "Fechar",
65-
"stats_total_messages": "Total de Mensagens",
70+
"stats_computing": "Calculando estatísticas...",
71+
"stats_messages": "Mensagens",
6672
"stats_participants": "Participantes",
73+
"stats_avg_day": "Média/Dia",
74+
"stats_media": "Mídia",
75+
"stats_duration": "Duração",
76+
"stats_total_messages": "Total de Mensagens",
6777
"stats_media_shared": "Mídias Compartilhadas",
6878
"stats_date_range": "Período",
6979
"stats_avg_per_day": "Média por dia",
@@ -98,8 +108,10 @@
98108

99109
"transcription_title": "Transcrição",
100110
"transcription_transcribe": "Transcrever áudio",
111+
"transcription_transcribe_title": "Transcrever esta mensagem de voz usando IA",
101112
"transcription_show": "Mostrar transcrição",
102113
"transcription_hide": "Ocultar transcrição",
114+
"transcription_hide_title": "Ocultar transcrição",
103115
"transcription_loading_model": "Carregando modelo de IA... {progress}%",
104116
"transcription_transcribing": "Transcrevendo...",
105117
"transcription_retry": "Tentar novamente",
@@ -111,6 +123,9 @@
111123
"auto_load_media": "Carregar Mídia Automaticamente",
112124

113125
"context_menu_remove": "Remover Conversa",
126+
"context_menu_close": "Fechar menu de contexto",
127+
128+
"participants_view_all": "Clique para ver todos os participantes",
114129

115130
"export_instructions_title": "Como exportar sua conversa do WhatsApp",
116131
"export_step_1": "Abra o WhatsApp no seu celular",
@@ -142,5 +157,6 @@
142157
"language_chinese": "中文",
143158
"language_korean": "한국어",
144159
"language_russian": "Русский",
145-
"language_auto": "Detectar automaticamente"
160+
"language_auto": "Detectar automaticamente",
161+
"language_change": "Mudar idioma"
146162
}

src/lib/components/BookmarkModal.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import { untrack } from 'svelte';
33
import { type Bookmark, bookmarksState } from '$lib/bookmarks.svelte';
4+
import * as m from '$lib/paraglide/messages';
45
56
// Data needed to create a new bookmark
67
interface NewBookmarkData {
@@ -129,7 +130,7 @@ const displayPreview = $derived(
129130
type="button"
130131
class="p-1 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:text-gray-300 dark:hover:bg-gray-700 transition-colors cursor-pointer"
131132
onclick={onClose}
132-
aria-label="Close"
133+
aria-label={m.bookmarks_close_modal()}
133134
>
134135
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
135136
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@@ -163,7 +164,7 @@ const displayPreview = $derived(
163164
id="comment"
164165
rows="4"
165166
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[var(--color-whatsapp-teal)] focus:border-transparent resize-none"
166-
placeholder="Add a note to this bookmark..."
167+
placeholder={m.bookmarks_note_placeholder()}
167168
></textarea>
168169
<p class="mt-1 text-xs text-gray-400">
169170
Press <kbd class="px-1 py-0.5 bg-gray-100 dark:bg-gray-700 rounded text-xs">⌘</kbd>+<kbd class="px-1 py-0.5 bg-gray-100 dark:bg-gray-700 rounded text-xs">Enter</kbd> to save

src/lib/components/BookmarksPanel.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import { type Bookmark, bookmarksState } from '$lib/bookmarks.svelte';
33
import BookmarkModal from './BookmarkModal.svelte';
4+
import * as m from '$lib/paraglide/messages';
45
56
interface Props {
67
currentChatId?: string;
@@ -138,7 +139,7 @@ function handleKeydown(e: KeyboardEvent) {
138139
type="button"
139140
class="p-1.5 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:text-gray-300 dark:hover:bg-gray-700 transition-colors cursor-pointer"
140141
onclick={onClose}
141-
aria-label="Close bookmarks panel"
142+
aria-label={m.bookmarks_close()}
142143
>
143144
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
144145
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@@ -187,10 +188,10 @@ function handleKeydown(e: KeyboardEvent) {
187188
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"/>
188189
</svg>
189190
<p class="text-gray-500 dark:text-gray-400 text-sm">
190-
{filterMode === 'current' ? 'No bookmarks in this chat' : 'No bookmarks yet'}
191+
{filterMode === 'current' ? m.bookmarks_empty_current_chat() : m.bookmarks_empty()}
191192
</p>
192193
<p class="text-gray-400 dark:text-gray-500 text-xs mt-1">
193-
Click the bookmark icon on any message to save it
194+
{m.bookmarks_empty_hint()}
194195
</p>
195196
</div>
196197
{:else}

src/lib/components/ChatList.svelte

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { floating } from '$lib/actions/floating';
33
import type { ChatData } from '$lib/state.svelte';
44
import { getAvailableLanguages } from '$lib/transcription.svelte';
5+
import * as m from '$lib/paraglide/messages';
56
67
interface LoadingChat {
78
id: string;
@@ -152,8 +153,8 @@ function getLastMessage(chat: ChatData): string {
152153
<div class="flex-1 overflow-y-auto">
153154
{#if chats.length === 0 && loadingChats.length === 0}
154155
<div class="p-4 text-center text-gray-500 dark:text-gray-400">
155-
<p>No chats loaded</p>
156-
<p class="text-sm mt-1">Import a WhatsApp export to get started</p>
156+
<p>{m.chats_no_loaded()}</p>
157+
<p class="text-sm mt-1">{m.chats_import_hint()}</p>
157158
</div>
158159
{:else}
159160
<!-- Loading chat placeholders -->
@@ -242,8 +243,8 @@ function getLastMessage(chat: ChatData): string {
242243
e.stopPropagation();
243244
onRemove(index);
244245
}}
245-
title="Remove chat"
246-
aria-label="Remove chat"
246+
title={m.chat_remove()}
247+
aria-label={m.chat_remove()}
247248
>
248249
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
249250
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@@ -256,8 +257,8 @@ function getLastMessage(chat: ChatData): string {
256257
e.stopPropagation();
257258
openContextMenu(e, index, e.currentTarget as HTMLButtonElement);
258259
}}
259-
title="Chat options"
260-
aria-label="Chat options"
260+
title={m.chat_options()}
261+
aria-label={m.chat_options()}
261262
>
262263
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
263264
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
@@ -276,7 +277,7 @@ function getLastMessage(chat: ChatData): string {
276277
type="button"
277278
class="fixed inset-0 z-40 cursor-default"
278279
onclick={closeContextMenu}
279-
aria-label="Close context menu"
280+
aria-label={m.context_menu_close()}
280281
></button>
281282

282283
<!-- Menu -->
@@ -346,7 +347,7 @@ function getLastMessage(chat: ChatData): string {
346347
role="menu"
347348
>
348349
<div class="px-3 py-2 border-b border-gray-200 dark:border-gray-700">
349-
<span class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Select Language</span>
350+
<span class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{m.transcription_select_language()}</span>
350351
</div>
351352
{#each availableLanguages as lang}
352353
{@const currentLang = getLanguageForChat(chats[contextMenuIndex]?.title || '')}

src/lib/components/ChatStats.svelte

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import { onMount } from 'svelte';
33
import type { ChatData } from '$lib/state.svelte';
4+
import * as m from '$lib/paraglide/messages';
45
56
interface Props {
67
chat: ChatData;
@@ -116,11 +117,11 @@ const maxHourCount = $derived(stats ? Math.max(...stats.messagesByHour) : 0);
116117
>
117118
<!-- Header -->
118119
<div class="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
119-
<h2 id="stats-title" class="text-xl font-bold text-gray-800 dark:text-white">Chat Statistics</h2>
120+
<h2 id="stats-title" class="text-xl font-bold text-gray-800 dark:text-white">{m.stats_title()}</h2>
120121
<button
121122
class="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
122123
onclick={onClose}
123-
aria-label="Close"
124+
aria-label={m.stats_close()}
124125
>
125126
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
126127
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@@ -134,7 +135,7 @@ const maxHourCount = $derived(stats ? Math.max(...stats.messagesByHour) : 0);
134135
<!-- Loading state -->
135136
<div class="flex flex-col items-center justify-center py-16">
136137
<div class="w-12 h-12 border-4 border-gray-200 dark:border-gray-700 border-t-[var(--color-whatsapp-teal)] rounded-full animate-spin mb-4"></div>
137-
<p class="text-gray-500 dark:text-gray-400">Computing statistics...</p>
138+
<p class="text-gray-500 dark:text-gray-400">{m.stats_computing()}</p>
138139
</div>
139140
{:else if error}
140141
<!-- Error state -->
@@ -151,31 +152,31 @@ const maxHourCount = $derived(stats ? Math.max(...stats.messagesByHour) : 0);
151152
<div class="text-2xl font-bold text-[var(--color-whatsapp-teal)]">
152153
{chat.messageCount.toLocaleString()}
153154
</div>
154-
<div class="text-sm text-gray-500 dark:text-gray-400">Messages</div>
155+
<div class="text-sm text-gray-500 dark:text-gray-400">{m.stats_messages()}</div>
155156
</div>
156157
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 text-center">
157158
<div class="text-2xl font-bold text-[var(--color-whatsapp-teal)]">
158159
{chat.participants.length}
159160
</div>
160-
<div class="text-sm text-gray-500 dark:text-gray-400">Participants</div>
161+
<div class="text-sm text-gray-500 dark:text-gray-400">{m.stats_participants()}</div>
161162
</div>
162163
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 text-center">
163164
<div class="text-2xl font-bold text-[var(--color-whatsapp-teal)]">
164165
{stats.avgMessagesPerDay}
165166
</div>
166-
<div class="text-sm text-gray-500 dark:text-gray-400">Avg/Day</div>
167+
<div class="text-sm text-gray-500 dark:text-gray-400">{m.stats_avg_day()}</div>
167168
</div>
168169
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 text-center">
169170
<div class="text-2xl font-bold text-[var(--color-whatsapp-teal)]">
170171
{chat.mediaCount}
171172
</div>
172-
<div class="text-sm text-gray-500 dark:text-gray-400">Media</div>
173+
<div class="text-sm text-gray-500 dark:text-gray-400">{m.stats_media()}</div>
173174
</div>
174175
</div>
175176

176177
<!-- Duration -->
177178
<div class="mb-8">
178-
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-2">Duration</h3>
179+
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-2">{m.stats_duration()}</h3>
179180
<p class="text-gray-600 dark:text-gray-300">
180181
{formatDuration(chat.startDate, chat.endDate)}
181182
</p>
@@ -188,7 +189,7 @@ const maxHourCount = $derived(stats ? Math.max(...stats.messagesByHour) : 0);
188189

189190
<!-- Messages by participant -->
190191
<div class="mb-8">
191-
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">Messages by Participant</h3>
192+
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">{m.stats_messages_by_participant()}</h3>
192193
<div class="space-y-3">
193194
{#each participantEntries as [name, count] (name)}
194195
<div>
@@ -209,15 +210,15 @@ const maxHourCount = $derived(stats ? Math.max(...stats.messagesByHour) : 0);
209210

210211
<!-- Most active hour -->
211212
<div class="mb-8">
212-
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-2">Most Active Hour</h3>
213+
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-2">{m.stats_busiest_hour()}</h3>
213214
<p class="text-gray-600 dark:text-gray-300">
214215
{formatHour(stats.mostActiveHour)}
215216
</p>
216217
</div>
217218

218219
<!-- Activity by hour -->
219220
<div>
220-
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">Activity by Hour</h3>
221+
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">{m.stats_activity_by_hour()}</h3>
221222
<div class="flex items-end gap-0.5 h-24">
222223
{#each stats.messagesByHour as count, hour (hour)}
223224
{@const height = maxHourCount > 0 ? (count / maxHourCount) * 100 : 0}

src/lib/components/ChatView.svelte

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { bookmarksState } from '$lib/bookmarks.svelte';
44
import type { ChatMessage } from '$lib/parser';
55
import { groupMessagesByDate } from '$lib/parser';
66
import MessageBubble from './MessageBubble.svelte';
7+
import * as m from '$lib/paraglide/messages';
78
89
interface Props {
910
messages: ChatMessage[];
@@ -314,9 +315,9 @@ function handleScroll() {
314315
<div class="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-2">
315316
{#if isLoadingMore}
316317
<div class="w-4 h-4 border-2 border-gray-300 border-t-[var(--color-whatsapp-teal)] rounded-full animate-spin"></div>
317-
Loading older messages...
318-
{:else}
319-
<span class="opacity-60">Scroll up for older messages</span>
318+
{m.messages_loading_older()}
319+
{:else}
320+
<span class="opacity-60">{m.messages_scroll_older()}</span>
320321
{/if}
321322
</div>
322323
</div>
@@ -358,7 +359,7 @@ function handleScroll() {
358359

359360
{#if messages.length === 0}
360361
<div class="flex items-center justify-center h-full text-gray-500 dark:text-gray-400">
361-
<p>No messages to display</p>
362+
<p>{m.messages_no_display()}</p>
362363
</div>
363364
{/if}
364365
</div>

src/lib/components/LocaleSwitcher.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
locales,
77
setLocale,
88
} from '$lib/paraglide/runtime';
9+
import * as m from '$lib/paraglide/messages';
910
1011
const LOCALE_STORAGE_KEY = 'locale';
1112
@@ -59,7 +60,7 @@ function handleClickOutside() {
5960
: 'bg-gray-100/80 dark:bg-gray-800/80 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400 backdrop-blur-sm'}"
6061
aria-haspopup="listbox"
6162
aria-expanded={dropdownOpen}
62-
aria-label="Change language"
63+
aria-label={m.language_change()}
6364
>
6465
<span class="w-4 h-4 flex items-center justify-center text-[10px] font-semibold">{currentLocale.toUpperCase()}</span>
6566
</button>

0 commit comments

Comments
 (0)