opencode-mem 2.11.3 → 2.11.6

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.
package/dist/web/app.js CHANGED
@@ -57,8 +57,8 @@ function populateTagDropdowns() {
57
57
  const tagFilter = document.getElementById("tag-filter");
58
58
  const addTag = document.getElementById("add-tag");
59
59
 
60
- tagFilter.innerHTML = '<option value="">All Tags</option>';
61
- addTag.innerHTML = '<option value="">Select tag</option>';
60
+ tagFilter.innerHTML = `<option value="">${t("opt-all-tags")}</option>`;
61
+ addTag.innerHTML = `<option value="">${t("opt-select-tag")}</option>`;
62
62
 
63
63
  const scopeTags = state.tags.project;
64
64
 
@@ -83,7 +83,7 @@ function renderMemories() {
83
83
  const container = document.getElementById("memories-list");
84
84
 
85
85
  if (state.memories.length === 0) {
86
- container.innerHTML = '<div class="empty-state">No memories found</div>';
86
+ container.innerHTML = `<div class="empty-state">${t("empty-memories")}</div>`;
87
87
  return;
88
88
  }
89
89
 
@@ -162,14 +162,13 @@ function renderCombinedCard(pair) {
162
162
  memory.updatedAt && memory.updatedAt !== memory.createdAt ? formatDate(memory.updatedAt) : null;
163
163
 
164
164
  const dateInfo = updatedDate
165
- ? `<span>Created: ${createdDate}</span><span>Updated: ${updatedDate}</span>`
166
- : `<span>Created: ${createdDate}</span>`;
167
-
165
+ ? `<span>${t("date-created")} ${createdDate}</span><span>${t("date-updated")} ${updatedDate}</span>`
166
+ : `<span>${t("date-created")} ${createdDate}</span>`;
168
167
  return `
169
168
  <div class="combined-card ${isSelected ? "selected" : ""} ${isPinned ? "pinned" : ""}" data-id="${memory.id}">
170
169
  <div class="combined-prompt-section">
171
170
  <div class="combined-header">
172
- <span class="badge badge-prompt">USER PROMPT</span>
171
+ <span class="badge badge-prompt">${t("badge-prompt")}</span>
173
172
  <span class="prompt-date">${formatDate(prompt.createdAt)}</span>
174
173
  </div>
175
174
  <div class="prompt-content">${escapeHtml(prompt.content)}</div>
@@ -183,17 +182,17 @@ function renderCombinedCard(pair) {
183
182
  <div class="memory-header">
184
183
  <div class="meta">
185
184
  <input type="checkbox" class="memory-checkbox" data-id="${memory.id}" ${isSelected ? "checked" : ""} />
186
- <span class="badge badge-memory">MEMORY</span>
185
+ <span class="badge badge-memory">${t("badge-memory")}</span>
187
186
  ${memory.memoryType ? `<span class="badge badge-type">${memory.memoryType}</span>` : ""}
188
187
  ${similarityHtml}
189
- ${isPinned ? '<span class="badge badge-pinned">PINNED</span>' : ""}
188
+ ${isPinned ? `<span class="badge badge-pinned">${t("badge-pinned")}</span>` : ""}
190
189
  <span class="memory-display-name">${escapeHtml(memory.displayName || memory.id)}</span>
191
190
  </div>
192
191
  <div class="memory-actions">
193
192
  ${pinButton}
194
193
  <button class="btn-edit" onclick="editMemory('${memory.id}')"><i data-lucide="edit-3" class="icon"></i></button>
195
194
  <button class="btn-delete" onclick="deleteMemoryWithLink('${memory.id}', true)">
196
- <i data-lucide="trash-2" class="icon"></i> Delete Pair
195
+ <i data-lucide="trash-2" class="icon"></i> ${t("btn-delete-pair")}
197
196
  </button>
198
197
  </div>
199
198
  </div>
@@ -219,21 +218,21 @@ function renderPromptCard(prompt) {
219
218
  <div class="meta">
220
219
  <input type="checkbox" class="memory-checkbox" data-id="${prompt.id}" ${isSelected ? "checked" : ""} />
221
220
  <i data-lucide="message-circle" class="icon"></i>
222
- <span class="badge badge-prompt">USER PROMPT</span>
223
- ${isLinked ? '<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> LINKED</span>' : ""}
221
+ <span class="badge badge-prompt">${t("badge-prompt")}</span>
222
+ ${isLinked ? `<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> ${t("badge-linked")}</span>` : ""}
224
223
  <span class="prompt-date">${promptDate}</span>
225
224
  </div>
226
225
  <div class="prompt-actions">
227
226
  <button class="btn-delete" onclick="deletePromptWithLink('${prompt.id}', ${isLinked})">
228
227
  <i data-lucide="trash-2" class="icon"></i>
229
- ${isLinked ? "Delete Pair" : "Delete"}
228
+ ${isLinked ? t("btn-delete-pair") : t("btn-delete")}
230
229
  </button>
231
230
  </div>
232
231
  </div>
233
232
  <div class="prompt-content">
234
233
  ${escapeHtml(prompt.content)}
235
234
  </div>
236
- ${isLinked ? '<div class="link-indicator"><i data-lucide="arrow-down" class="icon-sm"></i> Generated memory above <i data-lucide="arrow-up" class="icon-sm"></i></div>' : ""}
235
+ ${isLinked ? `<div class="link-indicator"><i data-lucide="arrow-down" class="icon-sm"></i> ${t("text-generated-above")} <i data-lucide="arrow-up" class="icon-sm"></i></div>` : ""}
237
236
  </div>
238
237
  `;
239
238
  }
@@ -270,9 +269,8 @@ function renderMemoryCard(memory) {
270
269
  memory.updatedAt && memory.updatedAt !== memory.createdAt ? formatDate(memory.updatedAt) : null;
271
270
 
272
271
  const dateInfo = updatedDate
273
- ? `<span>Created: ${createdDate}</span><span>Updated: ${updatedDate}</span>`
274
- : `<span>Created: ${createdDate}</span>`;
275
-
272
+ ? `<span>${t("date-created")} ${createdDate}</span><span>${t("date-updated")} ${updatedDate}</span>`
273
+ : `<span>${t("date-created")} ${createdDate}</span>`;
276
274
  const tagsHtml =
277
275
  memory.tags && memory.tags.length > 0
278
276
  ? `<div class="tags-list">${memory.tags.map((t) => `<span class="tag-badge">${escapeHtml(t)}</span>`).join("")}</div>`
@@ -284,9 +282,9 @@ function renderMemoryCard(memory) {
284
282
  <div class="meta">
285
283
  <input type="checkbox" class="memory-checkbox" data-id="${memory.id}" ${isSelected ? "checked" : ""} />
286
284
  ${memory.memoryType ? `<span class="badge badge-type">${memory.memoryType}</span>` : ""}
287
- ${isLinked ? '<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> LINKED</span>' : ""}
285
+ ${isLinked ? `<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> ${t("badge-linked")}</span>` : ""}
288
286
  ${similarityHtml}
289
- ${isPinned ? '<span class="badge badge-pinned">PINNED</span>' : ""}
287
+ ${isPinned ? `<span class="badge badge-pinned">${t("badge-pinned")}</span>` : ""}
290
288
  <span class="memory-display-name">${escapeHtml(displayInfo)}</span>
291
289
  ${subtitle}
292
290
  </div>
@@ -295,13 +293,13 @@ function renderMemoryCard(memory) {
295
293
  <button class="btn-edit" onclick="editMemory('${memory.id}')"><i data-lucide="edit-3" class="icon"></i></button>
296
294
  <button class="btn-delete" onclick="deleteMemoryWithLink('${memory.id}', ${isLinked})">
297
295
  <i data-lucide="trash-2" class="icon"></i>
298
- ${isLinked ? "Delete Pair" : "Delete"}
296
+ ${isLinked ? t("btn-delete-pair") : t("btn-delete")}
299
297
  </button>
300
298
  </div>
301
299
  </div>
302
300
  ${tagsHtml}
303
301
  <div class="memory-content markdown-content">${renderMarkdown(memory.content)}</div>
304
- ${isLinked ? '<div class="link-indicator"><i data-lucide="arrow-up" class="icon-sm"></i> From prompt below <i data-lucide="arrow-down" class="icon-sm"></i></div>' : ""}
302
+ ${isLinked ? `<div class="link-indicator"><i data-lucide="arrow-up" class="icon-sm"></i> ${t("text-from-below")} <i data-lucide="arrow-down" class="icon-sm"></i></div>` : ""}
305
303
  <div class="memory-footer">
306
304
  ${dateInfo}
307
305
  <span>ID: ${memory.id}</span>
@@ -340,17 +338,16 @@ function updateBulkActions() {
340
338
 
341
339
  if (state.selectedMemories.size > 0) {
342
340
  bulkActions.classList.remove("hidden");
343
- selectedCount.textContent = `${state.selectedMemories.size} selected`;
341
+ selectedCount.textContent = t("text-selected", { count: state.selectedMemories.size });
344
342
  } else {
345
343
  bulkActions.classList.add("hidden");
346
344
  }
347
345
  }
348
346
 
349
347
  function updatePagination() {
350
- const pageInfo = `Page ${state.currentPage} of ${state.totalPages}`;
348
+ const pageInfo = t("text-page", { current: state.currentPage, total: state.totalPages });
351
349
  document.getElementById("page-info-top").textContent = pageInfo;
352
350
  document.getElementById("page-info-bottom").textContent = pageInfo;
353
-
354
351
  const hasPrev = state.currentPage > 1;
355
352
  const hasNext = state.currentPage < state.totalPages;
356
353
 
@@ -363,14 +360,16 @@ function updatePagination() {
363
360
  function updateSectionTitle() {
364
361
  const title = state.isSearching
365
362
  ? `└─ SEARCH RESULTS (${state.totalItems}) ──`
366
- : `└─ PROJECT MEMORIES (${state.totalItems}) ──`;
363
+ : t("section-project", { count: state.totalItems });
367
364
  document.getElementById("section-title").textContent = title;
368
365
  }
369
366
 
370
367
  async function loadStats() {
371
368
  const result = await fetchAPI("/api/stats");
372
369
  if (result.success) {
373
- document.getElementById("stats-total").textContent = `Total: ${result.data.total}`;
370
+ document.getElementById("stats-total").textContent = t("text-total", {
371
+ count: result.data.total,
372
+ });
374
373
  }
375
374
  }
376
375
 
@@ -389,7 +388,7 @@ async function addMemory(e) {
389
388
  : [];
390
389
 
391
390
  if (!content || !containerTag) {
392
- showToast("Content and tag are required", "error");
391
+ showToast(t("toast-add-error"), "error");
393
392
  return;
394
393
  }
395
394
 
@@ -400,12 +399,12 @@ async function addMemory(e) {
400
399
  });
401
400
 
402
401
  if (result.success) {
403
- showToast("Memory added successfully", "success");
402
+ showToast(t("toast-add-success"), "success");
404
403
  document.getElementById("add-form").reset();
405
404
  await loadMemories();
406
405
  await loadStats();
407
406
  } else {
408
- showToast(result.error || "Failed to add memory", "error");
407
+ showToast(result.error || t("toast-add-failed"), "error");
409
408
  }
410
409
  }
411
410
 
@@ -439,13 +438,12 @@ async function loadMemories() {
439
438
  updatePagination();
440
439
  updateSectionTitle();
441
440
  } else {
442
- showError(result.error || "Failed to load memories");
441
+ showError(result.error || t("toast-update-failed"));
443
442
  }
444
443
  }
445
444
 
446
445
  async function deleteMemoryWithLink(id, isLinked) {
447
- const message = isLinked ? "Delete this memory AND its linked prompt?" : "Delete this memory?";
448
-
446
+ const message = isLinked ? t("confirm-delete-pair") : t("confirm-delete");
449
447
  if (!confirm(message)) return;
450
448
 
451
449
  const result = await fetchAPI(`/api/memories/${id}?cascade=true`, {
@@ -453,22 +451,18 @@ async function deleteMemoryWithLink(id, isLinked) {
453
451
  });
454
452
 
455
453
  if (result.success) {
456
- const msg = result.data?.deletedPrompt ? "Memory and linked prompt deleted" : "Memory deleted";
457
- showToast(msg, "success");
454
+ showToast(t("toast-delete-success"), "success");
458
455
 
459
456
  state.selectedMemories.delete(id);
460
457
  await loadMemories();
461
458
  await loadStats();
462
459
  } else {
463
- showToast(result.error || "Failed to delete", "error");
460
+ showToast(result.error || t("toast-delete-failed"), "error");
464
461
  }
465
462
  }
466
463
 
467
464
  async function deletePromptWithLink(id, isLinked) {
468
- const message = isLinked
469
- ? "Delete this prompt AND its linked memory summary?"
470
- : "Delete this prompt?";
471
-
465
+ const message = isLinked ? t("confirm-delete-prompt") : t("confirm-delete");
472
466
  if (!confirm(message)) return;
473
467
 
474
468
  const result = await fetchAPI(`/api/prompts/${id}?cascade=true`, {
@@ -476,21 +470,20 @@ async function deletePromptWithLink(id, isLinked) {
476
470
  });
477
471
 
478
472
  if (result.success) {
479
- const msg = result.data?.deletedMemory ? "Prompt and linked memory deleted" : "Prompt deleted";
480
- showToast(msg, "success");
473
+ showToast(t("toast-delete-success"), "success");
481
474
 
482
475
  state.selectedMemories.delete(id);
483
476
  await loadMemories();
484
477
  await loadStats();
485
478
  } else {
486
- showToast(result.error || "Failed to delete", "error");
479
+ showToast(result.error || t("toast-delete-failed"), "error");
487
480
  }
488
481
  }
489
482
 
490
483
  async function bulkDelete() {
491
484
  if (state.selectedMemories.size === 0) return;
492
485
 
493
- const message = `Delete ${state.selectedMemories.size} selected items (including linked pairs)?`;
486
+ const message = t("confirm-bulk-delete", { count: state.selectedMemories.size });
494
487
  if (!confirm(message)) return;
495
488
 
496
489
  const ids = Array.from(state.selectedMemories);
@@ -518,7 +511,7 @@ async function bulkDelete() {
518
511
  if (result.success) deletedCount += result.data.deleted;
519
512
  }
520
513
 
521
- showToast(`Deleted ${deletedCount} items (including linked pairs)`, "success");
514
+ showToast(t("toast-bulk-delete-success"), "success");
522
515
  state.selectedMemories.clear();
523
516
  await loadMemories();
524
517
  await loadStats();
@@ -551,7 +544,7 @@ async function saveEdit(e) {
551
544
  const content = document.getElementById("edit-content").value.trim();
552
545
 
553
546
  if (!content) {
554
- showToast("Content is required", "error");
547
+ showToast(t("toast-add-error"), "error");
555
548
  return;
556
549
  }
557
550
 
@@ -562,11 +555,11 @@ async function saveEdit(e) {
562
555
  });
563
556
 
564
557
  if (result.success) {
565
- showToast("Memory updated", "success");
558
+ showToast(t("toast-update-success"), "success");
566
559
  closeModal();
567
560
  await loadMemories();
568
561
  } else {
569
- showToast(result.error || "Failed to update memory", "error");
562
+ showToast(result.error || t("toast-update-failed"), "error");
570
563
  }
571
564
  }
572
565
 
@@ -637,7 +630,8 @@ function showRefreshIndicator(show) {
637
630
 
638
631
  function formatDate(isoString) {
639
632
  const date = new Date(isoString);
640
- return date.toLocaleString("en-US", {
633
+ const locale = getLanguage() === "zh" ? "zh-CN" : "en-US";
634
+ return date.toLocaleString(locale, {
641
635
  year: "numeric",
642
636
  month: "short",
643
637
  day: "numeric",
@@ -650,10 +644,10 @@ async function pinMemory(id) {
650
644
  const result = await fetchAPI(`/api/memories/${id}/pin`, { method: "POST" });
651
645
 
652
646
  if (result.success) {
653
- showToast("Memory pinned", "success");
647
+ showToast(t("toast-update-success"), "success");
654
648
  await loadMemories();
655
649
  } else {
656
- showToast(result.error || "Failed to pin memory", "error");
650
+ showToast(result.error || t("toast-update-failed"), "error");
657
651
  }
658
652
  }
659
653
 
@@ -661,49 +655,40 @@ async function unpinMemory(id) {
661
655
  const result = await fetchAPI(`/api/memories/${id}/unpin`, { method: "POST" });
662
656
 
663
657
  if (result.success) {
664
- showToast("Memory unpinned", "success");
658
+ showToast(t("toast-update-success"), "success");
665
659
  await loadMemories();
666
660
  } else {
667
- showToast(result.error || "Failed to unpin memory", "error");
661
+ showToast(result.error || t("toast-update-failed"), "error");
668
662
  }
669
663
  }
670
664
 
671
665
  async function runCleanup() {
672
- if (!confirm("Run cleanup? This will delete old memories (respects pinned memories).")) return;
666
+ if (!confirm(t("confirm-cleanup"))) return;
673
667
 
674
- showToast("Running cleanup...", "info");
668
+ showToast(t("status-cleanup"), "info");
675
669
  const result = await fetchAPI("/api/cleanup", { method: "POST" });
676
670
 
677
671
  if (result.success) {
678
- const data = result.data;
679
- showToast(
680
- `Cleanup complete: ${data.deletedCount} deleted (user: ${data.userCount}, project: ${data.projectCount})`,
681
- "success"
682
- );
672
+ showToast(t("toast-cleanup-success"), "success");
683
673
  await loadMemories();
684
674
  await loadStats();
685
675
  } else {
686
- showToast(result.error || "Cleanup failed", "error");
676
+ showToast(result.error || t("toast-cleanup-failed"), "error");
687
677
  }
688
678
  }
689
679
 
690
680
  async function runDeduplication() {
691
- if (!confirm("Run deduplication? This will find and remove duplicate memories.")) return;
681
+ if (!confirm(t("confirm-dedup"))) return;
692
682
 
693
- showToast("Running deduplication...", "info");
683
+ showToast(t("status-dedup"), "info");
694
684
  const result = await fetchAPI("/api/deduplicate", { method: "POST" });
695
685
 
696
686
  if (result.success) {
697
- const data = result.data;
698
- let message = `Deduplication complete: ${data.exactDuplicatesDeleted} exact duplicates deleted`;
699
- if (data.nearDuplicateGroups.length > 0) {
700
- message += `, ${data.nearDuplicateGroups.length} near-duplicate groups found`;
701
- }
702
- showToast(message, "success");
687
+ showToast(t("toast-dedup-success"), "success");
703
688
  await loadMemories();
704
689
  await loadStats();
705
690
  } else {
706
- showToast(result.error || "Deduplication failed", "error");
691
+ showToast(result.error || t("toast-dedup-failed"), "error");
707
692
  }
708
693
  }
709
694
 
@@ -735,8 +720,7 @@ async function checkMigrationStatus() {
735
720
  function showTagMigrationModal(count) {
736
721
  const overlay = document.getElementById("tag-migration-overlay");
737
722
  const status = document.getElementById("tag-migration-status");
738
- status.textContent = `Found ${count} memories needing technical tags.`;
739
- overlay.classList.remove("hidden");
723
+ status.textContent = t("migration-found-tags", { count });
740
724
 
741
725
  document.getElementById("start-tag-migration-btn").onclick = runTagMigration;
742
726
  }
@@ -747,7 +731,7 @@ async function runTagMigration() {
747
731
  const progress = document.getElementById("tag-migration-progress");
748
732
 
749
733
  actions.classList.add("hidden");
750
- status.textContent = "Starting migration...";
734
+ status.textContent = t("status-migration-init");
751
735
  progress.style.width = "0%";
752
736
 
753
737
  let totalProcessed = 0;
@@ -764,8 +748,7 @@ async function runTagMigration() {
764
748
  });
765
749
 
766
750
  if (!result.success) {
767
- status.textContent = "Migration failed: " + result.error;
768
- actions.classList.remove("hidden");
751
+ status.textContent = t("toast-migration-failed") + ": " + result.error;
769
752
  return;
770
753
  }
771
754
 
@@ -775,22 +758,20 @@ async function runTagMigration() {
775
758
  const percent = total > 0 ? Math.round((totalProcessed / total) * 100) : 0;
776
759
 
777
760
  progress.style.width = percent + "%";
778
- status.textContent = `Processing memories... ${totalProcessed}/${total} (${percent}%)`;
779
-
761
+ status.textContent = t("status-migration-progress", { current: totalProcessed, total: total });
780
762
  if (hasMore) {
781
763
  await new Promise((resolve) => setTimeout(resolve, 100));
782
764
  }
783
765
  }
784
766
 
785
767
  if (attempts >= maxAttempts) {
786
- status.textContent = "Migration stopped: maximum attempts reached";
787
- actions.classList.remove("hidden");
768
+ status.textContent = t("migration-stopped");
788
769
  return;
789
770
  }
790
771
 
791
772
  progress.style.width = "100%";
792
- status.textContent = `Successfully tagged ${totalProcessed} memories!`;
793
- showToast("Migration complete", "success");
773
+ status.textContent = t("toast-migration-success");
774
+ showToast(t("toast-migration-success"), "success");
794
775
  setTimeout(() => {
795
776
  document.getElementById("tag-migration-overlay").classList.add("hidden");
796
777
  loadMemories();
@@ -804,11 +785,14 @@ function showMigrationWarning(data) {
804
785
 
805
786
  const shardInfo =
806
787
  data.shardMismatches.length > 0
807
- ? `${data.shardMismatches.length} shard(s) have different dimensions`
808
- : "dimension mismatch detected";
788
+ ? t("migration-shards-mismatch", { count: data.shardMismatches.length })
789
+ : t("migration-dimension-mismatch");
809
790
 
810
- message.textContent = `Model mismatch: Config uses ${data.configDimensions}D (${data.configModel}), but ${shardInfo}.`;
811
- section.classList.remove("hidden");
791
+ message.textContent = t("migration-mismatch-details", {
792
+ configDimensions: data.configDimensions,
793
+ configModel: data.configModel,
794
+ shardInfo,
795
+ });
812
796
 
813
797
  lucide.createIcons();
814
798
  }
@@ -826,7 +810,7 @@ async function runMigration(strategy) {
826
810
  const checkbox = document.getElementById("migration-confirm-checkbox");
827
811
 
828
812
  if (!checkbox.checked) {
829
- showToast("Please confirm you understand this operation is irreversible", "error");
813
+ showToast(t("toast-migration-failed"), "error");
830
814
  return;
831
815
  }
832
816
 
@@ -841,8 +825,7 @@ async function runMigration(strategy) {
841
825
  return;
842
826
  }
843
827
 
844
- showToast("Running migration... This may take a while.", "info");
845
-
828
+ showToast(t("status-migration-init"), "info");
846
829
  const result = await fetchAPI("/api/migration/run", {
847
830
  method: "POST",
848
831
  headers: { "Content-Type": "application/json" },
@@ -859,15 +842,14 @@ async function runMigration(strategy) {
859
842
  message += `Re-embedded ${data.reEmbeddedMemories} memories. Duration: ${(data.duration / 1000).toFixed(2)}s`;
860
843
  }
861
844
 
862
- showToast(message, "success");
863
-
845
+ showToast(t("toast-migration-success"), "success");
864
846
  document.getElementById("migration-section").classList.add("hidden");
865
847
  document.getElementById("migration-confirm-checkbox").checked = false;
866
848
 
867
849
  await loadMemories();
868
850
  await loadStats();
869
851
  } else {
870
- showToast(result.error || "Migration failed", "error");
852
+ showToast(result.error || t("toast-migration-failed"), "error");
871
853
  }
872
854
  }
873
855
 
@@ -877,7 +859,7 @@ async function loadUserProfile() {
877
859
  state.userProfile = result.data;
878
860
  renderUserProfile();
879
861
  } else {
880
- showError(result.error || "Failed to load profile");
862
+ showError(result.error || t("toast-update-failed"));
881
863
  }
882
864
  }
883
865
 
@@ -937,15 +919,15 @@ function renderUserProfile() {
937
919
  <h3>${profile.displayName || profile.userId}</h3>
938
920
  <div class="profile-stats">
939
921
  <div class="stat-pill">
940
- <span class="label">VERSION</span>
922
+ <span class="label">${t("profile-version")}</span>
941
923
  <span class="value">${profile.version}</span>
942
924
  </div>
943
925
  <div class="stat-pill">
944
- <span class="label">PROMPTS</span>
926
+ <span class="label">${t("profile-prompts")}</span>
945
927
  <span class="value">${profile.totalPromptsAnalyzed}</span>
946
928
  </div>
947
929
  <div class="stat-pill">
948
- <span class="label">LAST UPDATED</span>
930
+ <span class="label">${t("profile-updated")}</span>
949
931
  <span class="value">${formatDate(profile.lastAnalyzedAt)}</span>
950
932
  </div>
951
933
  </div>
@@ -957,10 +939,10 @@ function renderUserProfile() {
957
939
 
958
940
  <div class="dashboard-grid">
959
941
  <div class="dashboard-section preferences-section">
960
- <h4><i data-lucide="heart" class="icon"></i> PREFERENCES <span class="count">${preferences.length}</span></h4>
942
+ <h4><i data-lucide="heart" class="icon"></i> ${t("profile-preferences")} <span class="count">${preferences.length}</span></h4>
961
943
  ${
962
944
  preferences.length === 0
963
- ? '<p class="empty-text">No preferences learned yet</p>'
945
+ ? `<p class="empty-text">${t("empty-preferences")}</p>`
964
946
  : `
965
947
  <div class="cards-grid">
966
948
  ${preferences
@@ -997,10 +979,10 @@ function renderUserProfile() {
997
979
  </div>
998
980
 
999
981
  <div class="dashboard-section patterns-section">
1000
- <h4><i data-lucide="activity" class="icon"></i> PATTERNS <span class="count">${patterns.length}</span></h4>
982
+ <h4><i data-lucide="activity" class="icon"></i> ${t("profile-patterns")} <span class="count">${patterns.length}</span></h4>
1001
983
  ${
1002
984
  patterns.length === 0
1003
- ? '<p class="empty-text">No patterns detected yet</p>'
985
+ ? `<p class="empty-text">${t("empty-patterns")}</p>`
1004
986
  : `
1005
987
  <div class="cards-grid">
1006
988
  ${patterns
@@ -1023,10 +1005,10 @@ function renderUserProfile() {
1023
1005
  </div>
1024
1006
 
1025
1007
  <div class="dashboard-section workflows-section full-width">
1026
- <h4><i data-lucide="workflow" class="icon"></i> WORKFLOWS <span class="count">${workflows.length}</span></h4>
1008
+ <h4><i data-lucide="workflow" class="icon"></i> ${t("profile-workflows")} <span class="count">${workflows.length}</span></h4>
1027
1009
  ${
1028
1010
  workflows.length === 0
1029
- ? '<p class="empty-text">No workflows identified yet</p>'
1011
+ ? `<p class="empty-text">${t("empty-workflows")}</p>`
1030
1012
  : `
1031
1013
  <div class="workflows-grid">
1032
1014
  ${workflows
@@ -1067,8 +1049,7 @@ async function showChangelog() {
1067
1049
  const list = document.getElementById("changelog-list");
1068
1050
 
1069
1051
  modal.classList.remove("hidden");
1070
- list.innerHTML = '<div class="loading">Loading changelog...</div>';
1071
-
1052
+ list.innerHTML = `<div class="loading">${t("loading-changelog")}</div>`;
1072
1053
  const result = await fetchAPI(
1073
1054
  `/api/user-profile/changelog?profileId=${state.userProfile.id}&limit=10`
1074
1055
  );
@@ -1089,12 +1070,12 @@ async function showChangelog() {
1089
1070
  )
1090
1071
  .join("");
1091
1072
  } else {
1092
- list.innerHTML = '<div class="empty-state">No changelog available</div>';
1073
+ list.innerHTML = `<div class="empty-state">${t("empty-changelog")}</div>`;
1093
1074
  }
1094
1075
  }
1095
1076
 
1096
1077
  async function refreshProfile() {
1097
- showToast("Refreshing profile...", "info");
1078
+ showToast(t("loading-profile"), "info");
1098
1079
  const result = await fetchAPI("/api/user-profile/refresh", {
1099
1080
  method: "POST",
1100
1081
  headers: { "Content-Type": "application/json" },
@@ -1105,7 +1086,7 @@ async function refreshProfile() {
1105
1086
  showToast(result.data.message, "success");
1106
1087
  await loadUserProfile();
1107
1088
  } else {
1108
- showToast(result.error || "Failed to refresh profile", "error");
1089
+ showToast(result.error || t("toast-update-failed"), "error");
1109
1090
  }
1110
1091
  }
1111
1092
 
@@ -1144,6 +1125,18 @@ document.addEventListener("DOMContentLoaded", async () => {
1144
1125
  document.getElementById("changelog-modal").classList.add("hidden");
1145
1126
  });
1146
1127
 
1128
+ document.getElementById("lang-toggle").addEventListener("click", () => {
1129
+ const newLang = getLanguage() === "en" ? "zh" : "en";
1130
+ setLanguage(newLang);
1131
+ document.getElementById("lang-toggle").textContent = newLang.toUpperCase();
1132
+ // Re-render dynamic content
1133
+ loadMemories();
1134
+ loadStats();
1135
+ if (state.currentView === "profile") loadUserProfile();
1136
+ });
1137
+
1138
+ document.getElementById("lang-toggle").textContent = getLanguage().toUpperCase();
1139
+
1147
1140
  document.getElementById("tag-filter").addEventListener("change", () => {
1148
1141
  state.selectedTag = document.getElementById("tag-filter").value;
1149
1142
  state.currentPage = 1;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/web/i18n.js"],"names":[],"mappings":""}