One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links 📅 calendar.xyehr.cn
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #184 from EvanTechDev/feature/fix-issues-in-language-packs-and-buttons

fix: i18n

authored by

Evan Huang and committed by
GitHub
04bccd7e 8229b5b7

+80 -59
+44 -1
components/app/calendar.tsx
··· 445 445 setDeleteConfirmOpen(true); 446 446 }; 447 447 448 - const confirmEventDelete = () => { 448 + 449 + 450 + const cleanupSharesForEvent = async (eventId: string) => { 451 + const storedShares = await readEncryptedLocalStorage<any[]>("shared-events", []); 452 + const relatedShares = storedShares.filter((share: any) => share?.eventId === eventId); 453 + if (!relatedShares.length) return; 454 + 455 + const results = await Promise.allSettled( 456 + relatedShares.map((share: any) => 457 + fetch("/api/share", { 458 + method: "DELETE", 459 + headers: { "Content-Type": "application/json" }, 460 + body: JSON.stringify({ id: share.id }), 461 + }), 462 + ), 463 + ); 464 + 465 + await writeEncryptedLocalStorage( 466 + "shared-events", 467 + storedShares.filter((share: any) => share?.eventId !== eventId), 468 + ); 469 + 470 + const failed = results.filter( 471 + (result) => 472 + result.status === "rejected" || 473 + (result.status === "fulfilled" && !result.value.ok), 474 + ); 475 + 476 + if (failed.length) { 477 + toast.error(t.shareDeleteFailed, { 478 + description: t.shareDeletePartialFailedDescription, 479 + }); 480 + } 481 + }; 482 + 483 + const confirmEventDelete = async () => { 449 484 if (!pendingDeleteEvent) return; 450 485 451 486 const deletedEvent = pendingDeleteEvent; 487 + try { 488 + await cleanupSharesForEvent(deletedEvent.id); 489 + } catch { 490 + toast.error(t.shareDeleteFailed, { 491 + description: t.shareCleanupErrorDescription, 492 + }); 493 + } 494 + 452 495 setEvents((prevEvents) => 453 496 prevEvents.filter((event) => event.id !== deletedEvent.id), 454 497 );
+2 -41
components/app/event/event-preview.tsx
··· 415 415 416 416 const handleDialogClick = (e: React.MouseEvent) => e.stopPropagation(); 417 417 418 - const cleanupSharesForEvent = async () => { 419 - const storedShares = await readEncryptedLocalStorage<any[]>("shared-events", []); 420 - const relatedShares = storedShares.filter((s: any) => s?.eventId === event.id); 421 - if (!relatedShares.length) return; 422 - 423 - const results = await Promise.allSettled( 424 - relatedShares.map((s: any) => 425 - fetch("/api/share", { 426 - method: "DELETE", 427 - headers: { "Content-Type": "application/json" }, 428 - body: JSON.stringify({ id: s.id }), 429 - }) 430 - ) 431 - ); 432 - 433 - await writeEncryptedLocalStorage( 434 - "shared-events", 435 - storedShares.filter((s: any) => s?.eventId !== event.id), 436 - ); 437 - 438 - const failed = results.filter( 439 - (r) => r.status === "rejected" || (r.status === "fulfilled" && !(r.value as Response).ok) 440 - ); 441 - 442 - if (failed.length) { 443 - toast.error(t.shareDeleteFailed, { 444 - description: t.shareDeletePartialFailedDescription, 445 - }); 446 - } 447 - }; 448 - 449 - const handleDeleteClick = async (e: React.MouseEvent) => { 418 + const handleDeleteClick = (e: React.MouseEvent) => { 450 419 e.stopPropagation(); 451 - try { 452 - await cleanupSharesForEvent(); 453 - } catch { 454 - toast.error(t.shareDeleteFailed, { 455 - description: t.shareCleanupErrorDescription, 456 - }); 457 - } finally { 458 - onDelete(); 459 - } 420 + onDelete(); 460 421 }; 461 422 462 423 return (
+2 -1
components/app/sidebar/countdown.tsx
··· 468 468 </Button> 469 469 <Button 470 470 variant="destructive" 471 - className="flex-1" 471 + className="flex-1 text-destructive-foreground hover:text-destructive-foreground" 472 472 onClick={() => deleteCountdown(selectedCountdown.id)} 473 473 > 474 474 <Trash2 className="mr-2 h-4 w-4" /> ··· 660 660 {selectedCountdown && ( 661 661 <Button 662 662 variant="destructive" 663 + className="text-destructive-foreground hover:text-destructive-foreground" 663 664 onClick={() => deleteCountdown(selectedCountdown.id)} 664 665 > 665 666 {t.countdownDelete}
+18 -2
hooks/useLocalStorage.ts
··· 329 329 }, [key]) 330 330 331 331 useEffect(() => { 332 + const refreshValue = () => { 333 + readLocalStorage(key, initialValue).then((value) => setStoredValue(value)) 334 + } 335 + 332 336 const unsubscribe = subscribeEncryptionState(() => { 333 337 const state = getEncryptionState() 334 338 if (state.ready) { 335 - readLocalStorage(key, initialValue).then((value) => setStoredValue(value)) 339 + refreshValue() 336 340 } 337 341 }) 338 - return unsubscribe 342 + 343 + const handleStorageWritten = (event: Event) => { 344 + const customEvent = event as CustomEvent<{ key?: string }> 345 + if (customEvent.detail?.key === key) { 346 + refreshValue() 347 + } 348 + } 349 + 350 + window.addEventListener("local-storage-written", handleStorageWritten) 351 + return () => { 352 + unsubscribe() 353 + window.removeEventListener("local-storage-written", handleStorageWritten) 354 + } 339 355 }, [key, initialValue]) 340 356 341 357 useEffect(() => {
+14 -14
locales/yue.json
··· 273 273 "less": "收埋", 274 274 "more": "更多", 275 275 "weekdays": [ 276 - "星期日", 277 - "星期一", 278 - "星期二", 279 - "星期三", 280 - "星期四", 281 - "星期五", 282 - "星期六" 276 + "日", 277 + "一", 278 + "二", 279 + "三", 280 + "四", 281 + "五", 282 + "六" 283 283 ], 284 284 "months": [ 285 285 "1月", ··· 456 456 "colorAmber": "琥珀色", 457 457 "colorTeal": "藍綠色", 458 458 "sidebarCalendarWeekdaysShort": [ 459 - "星期日", 460 - "星期一", 461 - "星期二", 462 - "星期三", 463 - "星期四", 464 - "星期五", 465 - "星期六" 459 + "日", 460 + "一", 461 + "二", 462 + "三", 463 + "四", 464 + "五", 465 + "六" 466 466 ], 467 467 "sidebarCalendarMonthsLong": [ 468 468 "1月",