From a6581ac7414a313bd98f0b19d5d413236abb8adc Mon Sep 17 00:00:00 2001 From: ShotaroKataoka Date: Thu, 9 Jul 2026 08:04:22 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat(web-ui):=20i18n=20=E2=80=94=20next-int?= =?UTF-8?q?l=20en/ja=20with=20locale=20switcher=20in=20Settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce next-intl (4.13) without i18n routing (compatible with static export). All user-facing strings in the main UI are externalized to messages/en.json and messages/ja.json. Key changes: - LocaleProvider (NextIntlClientProvider + localStorage persistence) wired at RootLayout level; locale auto-detected from browser language - Settings: Language section with English / 日本語 radio toggle - formatDate: locale-aware (en-US / ja-JP) - 30+ components migrated to useTranslations() - ToolCard / ToolIndicator: tool labels resolve via "tools" namespace with fallback to the existing label map for unknown tools - renderWithIntl test helper for components under the intl provider - ErrorBoundary refactored: class delegates render to functional ErrorFallback so it can call useTranslations() Verified: tsc clean, vitest 38 passed, Playwright E2E passed, build:cloud static export passed, make lint + make test (260) all green. Not yet externalized (lower priority): styles page, templates page, agentErrors.ts user messages (these have a Python parity test that would need parallel updates). --- web-ui/messages/en.json | 257 +++++++ web-ui/messages/ja.json | 257 +++++++ web-ui/package-lock.json | 716 +++++++++++++++++- web-ui/package.json | 1 + web-ui/src/app/(authenticated)/decks/page.tsx | 18 +- web-ui/src/app/layout.tsx | 7 +- web-ui/src/components/AppShell.tsx | 22 +- web-ui/src/components/ErrorBoundary.test.tsx | 3 +- web-ui/src/components/ErrorBoundary.tsx | 40 +- web-ui/src/components/Settings.tsx | 42 +- .../src/components/chat/AttachmentPreview.tsx | 6 +- web-ui/src/components/chat/ChatInput.tsx | 18 +- web-ui/src/components/chat/ChatMessage.tsx | 2 +- web-ui/src/components/chat/ChatPanel.tsx | 22 +- web-ui/src/components/chat/ChatPanelShell.tsx | 18 +- web-ui/src/components/chat/HearingCard.tsx | 12 +- web-ui/src/components/chat/MentionPopup.tsx | 4 +- web-ui/src/components/chat/ModeSelector.tsx | 27 +- web-ui/src/components/chat/ModelSelector.tsx | 4 +- web-ui/src/components/chat/PlusMenu.tsx | 8 +- web-ui/src/components/chat/SnippetBlock.tsx | 4 +- web-ui/src/components/chat/SnippetInput.tsx | 12 +- web-ui/src/components/chat/ToolCard.tsx | 11 +- web-ui/src/components/chat/ToolIndicator.tsx | 8 +- web-ui/src/components/deck/DeckActions.tsx | 22 +- web-ui/src/components/deck/DeckCard.tsx | 28 +- web-ui/src/components/deck/DeckListView.tsx | 35 +- web-ui/src/components/deck/OutlineView.tsx | 19 +- .../src/components/deck/SearchResultsGrid.tsx | 14 +- web-ui/src/components/deck/SlideCarousel.tsx | 32 +- web-ui/src/components/deck/SpecStepNav.tsx | 7 +- web-ui/src/components/deck/SpecWaiting.tsx | 10 +- web-ui/src/components/deck/WorkspaceView.tsx | 14 +- web-ui/src/i18n/LocaleProvider.tsx | 77 ++ web-ui/src/lib/utils.ts | 17 +- web-ui/src/test/renderWithIntl.tsx | 23 + 36 files changed, 1604 insertions(+), 213 deletions(-) create mode 100644 web-ui/messages/en.json create mode 100644 web-ui/messages/ja.json create mode 100644 web-ui/src/i18n/LocaleProvider.tsx create mode 100644 web-ui/src/test/renderWithIntl.tsx diff --git a/web-ui/messages/en.json b/web-ui/messages/en.json new file mode 100644 index 00000000..f46cd580 --- /dev/null +++ b/web-ui/messages/en.json @@ -0,0 +1,257 @@ +{ + "common": { + "cancel": "Cancel", + "delete": "Delete", + "close": "Close", + "save": "Save", + "retry": "Retry", + "loading": "Loading…", + "today": "Today", + "yesterday": "Yesterday", + "daysAgo": "{days}d ago" + }, + "appShell": { + "mainNavigation": "Main navigation", + "backToDecks": "Back to decks", + "decks": "Decks", + "styles": "Styles", + "templates": "Templates", + "openChat": "Open chat", + "closeChat": "Close chat", + "userMenu": "User menu", + "settings": "Settings", + "signOut": "Sign out" + }, + "deckList": { + "title": "Decks", + "presentationCount": "{count, plural, one {# presentation} other {# presentations}}", + "newDeck": "New Deck", + "searchPlaceholder": "Search slides across the organization…", + "clearSearch": "Clear search", + "tabs": { + "mine": "My Decks", + "favorites": "Favorites", + "shared": "Shared", + "public": "Internal" + }, + "emptyMineTitle": "No decks yet", + "emptyMineDescription": "Create your first presentation with AI assistance.", + "emptyMineAction": "Create your first deck", + "emptyOtherTitle": "No {tab} yet", + "emptyOtherDescription": "When decks appear here, you'll see them in this tab." + }, + "settings": { + "title": "Settings", + "language": "Language", + "languageDescription": "Display language for the interface" + }, + "deckCard": { + "addToFavorites": "Add to favorites", + "removeFromFavorites": "Remove from favorites", + "deckActions": "Deck actions", + "copyUrl": "Copy URL", + "openFolder": "Open Folder", + "openPptx": "Open PPTX", + "downloadPptx": "Download PPTX", + "makePrivate": "Make Private", + "makeInternal": "Make Internal", + "share": "Share", + "delete": "Delete", + "previewAlt": "Preview of {name}", + "internal": "Internal", + "private": "Private" + }, + "workspace": { + "slideCount": "{count, plural, one {# slide} other {# slides}}", + "lastEdited": "Last edited {date}", + "share": "Share", + "slideAlt": "Slide {number}", + "noSlides": "No slides yet. Use the chat to create slides." + }, + "deckActions": { + "internal": "Internal", + "private": "Private", + "share": "Share", + "shareInternallyTitle": "Share internally?", + "shareInternallyDescription": "This deck will be discoverable by everyone in the organization.", + "shareThisDeck": "Share this deck", + "searchByAlias": "Search by alias…", + "collaborators": "Collaborators", + "removeCollaborator": "Remove {name}", + "noCollaborators": "No collaborators yet" + }, + "search": { + "searching": "Searching slides across the organization…", + "resultsFound": "{count, plural, one {# result found} other {# results found}}", + "resultAlt": "{deck} page {page}", + "untitled": "Untitled", + "page": "Page {number}", + "noMatches": "No matching slides", + "noMatchesHint": "Try different keywords or a broader search term." + }, + "carousel": { + "errorOpenFolder": "Failed to open deck folder", + "errorOpenPptx": "Failed to open PPTX", + "buildingSlides": "Building your slides", + "buildingHint": "This usually takes a few seconds…", + "composing": "Composing slides…", + "previewsPlaceholder": "Slide previews will appear here after generating a PPTX.", + "preview": "Preview", + "fullSizeView": "Full size view", + "gridView": "Grid view", + "openFolder": "Open folder", + "openPptx": "Open PPTX", + "downloadPptx": "Download PPTX", + "slideAlt": "Slide {number}", + "slideAltFull": "Slide {number} of {total}" + }, + "outline": { + "evidence": "Evidence", + "visual": "Visual", + "notes": "Notes", + "emptyState": "Outline will appear here when the agent writes it." + }, + "chatInput": { + "maxFilesError": "Maximum 5 files can be attached at once.", + "uploadFailed": "Upload failed", + "textSnippet": "Text snippet", + "placeholderMobile": "Ask anything…", + "placeholderDesktop": "Ask anything… ⌘↵ send", + "inputLabel": "Chat message input", + "stopGeneration": "Stop generation", + "sendMessage": "Send message" + }, + "chatShell": { + "errorResetSession": "Failed to reset agent session", + "resizePanel": "Resize chat panel", + "chat": "Chat", + "newChat": "New chat", + "startNewChat": "Start new chat", + "closePanel": "Close chat panel", + "new": "New", + "deck": "Deck" + }, + "chat": { + "errorSaveHistory": "Failed to save chat history", + "historyTruncated": "Older messages in this conversation were omitted to keep loading fast.", + "letsPresent": "Let's present", + "emptyHint": "Drop a URL, paste notes, or describe your idea", + "example1": "Create a product pitch for our new feature", + "example2": "Turn my meeting notes into a status update deck", + "example3": "Make a 5-minute tech talk about a topic I'll describe", + "chatMessages": "Chat messages", + "placeholderMobile": "Ask anything…", + "placeholderDesktop": "Ask anything… ⌘↵ send", + "forceStop": "Force stop — partial report may be lost", + "mentionSuggestions": "Mention suggestions", + "errorSwitchModel": "Failed to switch model" + }, + "snippet": { + "title": "Text Snippet", + "placeholder": "Paste or type your text here…", + "characterCount": "{count, plural, one {# character} other {# characters}}", + "cancel": "Cancel", + "attach": "Attach", + "textSnippet": "Text snippet", + "charCount": "{count} chars" + }, + "plusMenu": { + "attachLabel": "Attach file or snippet", + "attachFile": "Attach file", + "textSnippet": "Text snippet" + }, + "attachments": { + "removeFile": "Remove {name}", + "removeSnippet": "Remove snippet" + }, + "hearing": { + "agentQuestions": "Agent questions", + "recommended": "Recommended", + "additionalNotes": "Additional notes...", + "additionalNotesLabel": "additional notes", + "submit": "Submit" + }, + "modeSelector": { + "vibeDescription": "Drop materials, get slides. Minimal interaction for quick conversions.", + "specDescription": "Plan first, then build. Refine requirements through dialogue before composing.", + "greatFor": "Great for", + "vibeFor1": "URLs & articles → slides", + "vibeFor2": "Papers & docs → summary deck", + "vibeFor3": "Meeting notes → presentation", + "specFor1": "Proposals & pitch decks", + "specFor2": "Projects needing structure", + "specFor3": "Polished, precise presentations" + }, + "tools": { + "running": "Running", + "completed": "Completed", + "failed": "Failed", + "create_deck": "Creating deck", + "write_slide": "Writing slide", + "remove_slide": "Removing slide", + "reorder_slides": "Reordering slides", + "clone_deck": "Cloning deck", + "clone_slide": "Cloning slide", + "read_reference": "Reading reference", + "list_references": "Listing patterns", + "search_icons": "Searching icons", + "search_slides": "Searching slides", + "get_deck": "Loading deck", + "web_search": "Web search", + "web_fetch": "Fetching page", + "read_uploaded_file": "Reading file", + "import_attachment": "Importing file", + "generate_pptx": "Generating PPTX", + "generate_preview": "Generating preview", + "measure_slides": "Measuring slides", + "init_presentation": "Initializing deck", + "analyze_template": "Analyzing template", + "start_presentation": "Starting workflow", + "list_templates": "Listing templates", + "list_styles": "Listing styles", + "apply_style": "Applying style", + "read_examples": "Reading example", + "list_workflows": "Listing workflows", + "read_workflows": "Reading workflow", + "list_guides": "Listing guides", + "read_guides": "Reading guide", + "search_assets": "Searching assets", + "list_asset_sources": "Listing asset sources", + "get_preview": "Getting preview", + "run_python": "Running code", + "run_style_python": "Building style", + "grid": "Computing layout", + "code_to_slide": "Code to slide", + "pptx_to_json": "Converting PPTX", + "hearing": "Asking questions", + "compose_slides": "Composing slides" + }, + "errorBoundary": { + "problem": "{label} ran into a problem.", + "thisSection": "This section", + "tryAgain": "Try again" + }, + "specNav": { + "specPhases": "Spec phases", + "brief": "Brief", + "outline": "Outline", + "artDirection": "Art Direction", + "slides": "Slides" + }, + "specWaiting": { + "draftingBrief": "Drafting the brief…", + "structuringOutline": "Structuring the outline…", + "composingArtDirection": "Composing art direction…" + }, + "decksPage": { + "removeFromFavorites": "Remove from favorites", + "addToFavorites": "Add to favorites", + "deleteTitle": "Delete this deck?", + "deleteDescription": "{name} will be permanently deleted after 30 days. This action cannot be undone.", + "delete": "Delete", + "newDeck": "New Deck", + "createNewDeck": "Create new deck", + "chat": "Chat", + "preview": "Preview" + } +} diff --git a/web-ui/messages/ja.json b/web-ui/messages/ja.json new file mode 100644 index 00000000..6a81f4dd --- /dev/null +++ b/web-ui/messages/ja.json @@ -0,0 +1,257 @@ +{ + "common": { + "cancel": "キャンセル", + "delete": "削除", + "close": "閉じる", + "save": "保存", + "retry": "再試行", + "loading": "読み込み中…", + "today": "今日", + "yesterday": "昨日", + "daysAgo": "{days}日前" + }, + "appShell": { + "mainNavigation": "メインナビゲーション", + "backToDecks": "デッキ一覧に戻る", + "decks": "デッキ", + "styles": "スタイル", + "templates": "テンプレート", + "openChat": "チャットを開く", + "closeChat": "チャットを閉じる", + "userMenu": "ユーザーメニュー", + "settings": "設定", + "signOut": "サインアウト" + }, + "deckList": { + "title": "デッキ", + "presentationCount": "{count, plural, other {# 件のプレゼンテーション}}", + "newDeck": "新規デッキ", + "searchPlaceholder": "組織全体のスライドを検索…", + "clearSearch": "検索をクリア", + "tabs": { + "mine": "マイデッキ", + "favorites": "お気に入り", + "shared": "共有", + "public": "社内公開" + }, + "emptyMineTitle": "デッキはまだありません", + "emptyMineDescription": "AI の支援で最初のプレゼンテーションを作りましょう。", + "emptyMineAction": "最初のデッキを作成", + "emptyOtherTitle": "{tab}はまだありません", + "emptyOtherDescription": "デッキが追加されるとこのタブに表示されます。" + }, + "settings": { + "title": "設定", + "language": "言語", + "languageDescription": "インターフェースの表示言語" + }, + "deckCard": { + "addToFavorites": "お気に入りに追加", + "removeFromFavorites": "お気に入りから削除", + "deckActions": "デッキ操作", + "copyUrl": "URL をコピー", + "openFolder": "フォルダを開く", + "openPptx": "PPTX を開く", + "downloadPptx": "PPTX をダウンロード", + "makePrivate": "非公開にする", + "makeInternal": "社内公開にする", + "share": "共有", + "delete": "削除", + "previewAlt": "{name} のプレビュー", + "internal": "社内公開", + "private": "非公開" + }, + "workspace": { + "slideCount": "{count, plural, other {# 枚のスライド}}", + "lastEdited": "最終編集 {date}", + "share": "共有", + "slideAlt": "スライド {number}", + "noSlides": "スライドはまだありません。チャットから作成できます。" + }, + "deckActions": { + "internal": "社内公開", + "private": "非公開", + "share": "共有", + "shareInternallyTitle": "社内に公開しますか?", + "shareInternallyDescription": "このデッキは組織内の全員が検索・閲覧できるようになります。", + "shareThisDeck": "このデッキを共有", + "searchByAlias": "エイリアスで検索…", + "collaborators": "共同編集者", + "removeCollaborator": "{name} を削除", + "noCollaborators": "共同編集者はまだいません" + }, + "search": { + "searching": "組織全体のスライドを検索中…", + "resultsFound": "{count, plural, other {# 件見つかりました}}", + "resultAlt": "{deck} {page} ページ", + "untitled": "無題", + "page": "{number} ページ", + "noMatches": "一致するスライドがありません", + "noMatchesHint": "別のキーワードやより広い検索語を試してください。" + }, + "carousel": { + "errorOpenFolder": "デッキフォルダを開けませんでした", + "errorOpenPptx": "PPTX を開けませんでした", + "buildingSlides": "スライドを生成しています", + "buildingHint": "通常は数秒で完了します…", + "composing": "スライドを作成中…", + "previewsPlaceholder": "PPTX を生成するとスライドプレビューがここに表示されます。", + "preview": "プレビュー", + "fullSizeView": "全画面表示", + "gridView": "グリッド表示", + "openFolder": "フォルダを開く", + "openPptx": "PPTX を開く", + "downloadPptx": "PPTX をダウンロード", + "slideAlt": "スライド {number}", + "slideAltFull": "スライド {number} / {total}" + }, + "outline": { + "evidence": "根拠", + "visual": "ビジュアル", + "notes": "メモ", + "emptyState": "エージェントがアウトラインを書くとここに表示されます。" + }, + "chatInput": { + "maxFilesError": "一度に添付できるファイルは 5 つまでです。", + "uploadFailed": "アップロード失敗", + "textSnippet": "テキストスニペット", + "placeholderMobile": "なんでも聞いてください…", + "placeholderDesktop": "なんでも聞いてください… ⌘↵ で送信", + "inputLabel": "Chat message input", + "stopGeneration": "生成を停止", + "sendMessage": "メッセージを送信" + }, + "chatShell": { + "errorResetSession": "エージェントセッションをリセットできませんでした", + "resizePanel": "チャットパネルの幅を変更", + "chat": "チャット", + "newChat": "新しいチャット", + "startNewChat": "新しいチャットを開始", + "closePanel": "チャットパネルを閉じる", + "new": "新規", + "deck": "デッキ" + }, + "chat": { + "errorSaveHistory": "チャット履歴を保存できませんでした", + "historyTruncated": "読み込みを速く保つため、古いメッセージを省略しました。", + "letsPresent": "プレゼンを作ろう", + "emptyHint": "URL やメモの貼り付け、アイデアの説明から始められます", + "example1": "新機能のプロダクトピッチを作って", + "example2": "会議メモを進捗報告のデッキにして", + "example3": "これから説明するテーマで 5 分の技術トークを作って", + "chatMessages": "Chat messages", + "placeholderMobile": "なんでも聞いてください…", + "placeholderDesktop": "なんでも聞いてください… ⌘↵ で送信", + "forceStop": "強制停止 — 進行中のレポートが失われる可能性があります", + "mentionSuggestions": "メンション候補", + "errorSwitchModel": "モデルを切り替えられませんでした" + }, + "snippet": { + "title": "テキストスニペット", + "placeholder": "テキストを貼り付けまたは入力…", + "characterCount": "{count, plural, other {# 文字}}", + "cancel": "キャンセル", + "attach": "添付", + "textSnippet": "テキストスニペット", + "charCount": "{count} 文字" + }, + "plusMenu": { + "attachLabel": "ファイルまたはスニペットを添付", + "attachFile": "ファイルを添付", + "textSnippet": "テキストスニペット" + }, + "attachments": { + "removeFile": "{name} を削除", + "removeSnippet": "スニペットを削除" + }, + "hearing": { + "agentQuestions": "エージェントからの質問", + "recommended": "おすすめ", + "additionalNotes": "補足があれば入力…", + "additionalNotesLabel": "補足メモ", + "submit": "送信" + }, + "modeSelector": { + "vibeDescription": "資料を渡すだけでスライドに。最小限の対話で素早く変換します。", + "specDescription": "まず計画、それから構築。対話で要件を固めてから作成します。", + "greatFor": "こんな用途に", + "vibeFor1": "URL・記事 → スライド", + "vibeFor2": "論文・ドキュメント → 要約デッキ", + "vibeFor3": "会議メモ → プレゼン資料", + "specFor1": "提案書・ピッチ資料", + "specFor2": "構成が重要なプロジェクト", + "specFor3": "磨き込まれた正確なプレゼン" + }, + "tools": { + "running": "実行中", + "completed": "完了", + "failed": "失敗", + "create_deck": "デッキを作成中", + "write_slide": "スライドを作成中", + "remove_slide": "スライドを削除中", + "reorder_slides": "スライドを並べ替え中", + "clone_deck": "デッキを複製中", + "clone_slide": "スライドを複製中", + "read_reference": "リファレンスを読込中", + "list_references": "パターン一覧を取得中", + "search_icons": "アイコンを検索中", + "search_slides": "スライドを検索中", + "get_deck": "デッキを読込中", + "web_search": "Web 検索中", + "web_fetch": "ページを取得中", + "read_uploaded_file": "ファイルを読込中", + "import_attachment": "ファイルを取込中", + "generate_pptx": "PPTX を生成中", + "generate_preview": "プレビューを生成中", + "measure_slides": "スライドを計測中", + "init_presentation": "デッキを初期化中", + "analyze_template": "テンプレートを分析中", + "start_presentation": "ワークフローを開始中", + "list_templates": "テンプレート一覧を取得中", + "list_styles": "スタイル一覧を取得中", + "apply_style": "スタイルを適用中", + "read_examples": "サンプルを読込中", + "list_workflows": "ワークフロー一覧を取得中", + "read_workflows": "ワークフローを読込中", + "list_guides": "ガイド一覧を取得中", + "read_guides": "ガイドを読込中", + "search_assets": "アセットを検索中", + "list_asset_sources": "アセットソース一覧を取得中", + "get_preview": "プレビューを取得中", + "run_python": "コードを実行中", + "run_style_python": "スタイルを構築中", + "grid": "レイアウトを計算中", + "code_to_slide": "コードをスライド化中", + "pptx_to_json": "PPTX を変換中", + "hearing": "質問を作成中", + "compose_slides": "スライドを構成中" + }, + "errorBoundary": { + "problem": "{label}で問題が発生しました。", + "thisSection": "このセクション", + "tryAgain": "再試行" + }, + "specNav": { + "specPhases": "スペックの段階", + "brief": "ブリーフ", + "outline": "アウトライン", + "artDirection": "アートディレクション", + "slides": "スライド" + }, + "specWaiting": { + "draftingBrief": "ブリーフを作成中…", + "structuringOutline": "アウトラインを構成中…", + "composingArtDirection": "アートディレクションを構成中…" + }, + "decksPage": { + "removeFromFavorites": "お気に入りから削除", + "addToFavorites": "お気に入りに追加", + "deleteTitle": "このデッキを削除しますか?", + "deleteDescription": "{name} は 30 日後に完全に削除されます。この操作は元に戻せません。", + "delete": "削除", + "newDeck": "新規デッキ", + "createNewDeck": "新しいデッキを作成", + "chat": "チャット", + "preview": "プレビュー" + } +} diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json index 1af9bf6f..eedb4fd8 100644 --- a/web-ui/package-lock.json +++ b/web-ui/package-lock.json @@ -24,6 +24,7 @@ "dompurify": "^3.4.9", "lucide-react": "^0.562.0", "next": "^16.2.6", + "next-intl": "^4.13.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "^19.2.1", @@ -1718,30 +1719,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", - "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -1953,6 +1930,36 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.6.tgz", + "integrity": "sha512-H5aexk1Le7T9TPmscacZ+1pR6CTa2n1wq+HDVGXhH8TzUlQQpeXzZs91dRtmFHrbeNbjPFPfQujUqm7MHgVoXQ==", + "license": "MIT" + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.12.tgz", + "integrity": "sha512-YyzzxVgYJ8DELmmkhn0Yr0rUj0dTJFf9Jp628K3S0ysInBWxLVDOS8i3RP91cCp4DMK4WYb4cVMhWA9i4knSJg==", + "license": "MIT", + "dependencies": { + "@formatjs/icu-skeleton-parser": "2.1.10" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.10.tgz", + "integrity": "sha512-XuSva+8ZGawk8VnD5VD6UeH8KarQ/Z022zgjHDoHmlNiAewstXuuzXc0Hk5pGFSdG+nNw5bfJKXqj1ZXHn9yUA==", + "license": "MIT" + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.10.tgz", + "integrity": "sha512-P/IC3qws3jH+1fEs+o0RIFgXKRaQlFehjS5W0FPAqdo6hgzawLl+eD0q0JjheQ3XtoOe5n8WSYfX06KQZI/QJA==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "3.1.6" + } + }, "node_modules/@hono/node-server": { "version": "1.19.14", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", @@ -2996,6 +3003,313 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@playwright/test": { "version": "1.61.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", @@ -4813,6 +5127,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -5126,6 +5446,204 @@ "dev": true, "license": "MIT" }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz", + "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz", + "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz", + "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz", + "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz", + "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz", + "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz", + "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz", + "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz", + "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz", + "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz", + "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz", + "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -5135,6 +5653,15 @@ "tslib": "^2.8.0" } }, + "node_modules/@swc/types": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@tailwindcss/node": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", @@ -8113,7 +8640,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -8754,6 +9280,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -10100,6 +10627,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/icu-minify": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.13.1.tgz", + "integrity": "sha512-nFYW2im0WJ3RUVZwabd71J8QTZRtkK1xxZBY7klg7a6KS/os17LZSj9q1VhbRSnk3S8Mv2I7F1izr/aEJ6cUsw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/icu-messageformat-parser": "^3.4.0" + } + }, "node_modules/idb": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/idb/-/idb-5.0.6.tgz", @@ -10201,6 +10743,16 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "11.2.9", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.9.tgz", + "integrity": "sha512-cGzymZerpDhVXRKjKLgXKda9gI29TU2o88L7gwNMHp3WZVxA/0c5tX52udXbW9JklDApolvMXZG6Dhhdz5eirA==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/fast-memoize": "3.1.6", + "@formatjs/icu-messageformat-parser": "3.5.12" + } + }, "node_modules/ip-address": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", @@ -10456,7 +11008,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10512,7 +11063,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -12761,7 +13311,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -12820,6 +13369,83 @@ } } }, + "node_modules/next-intl": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.13.1.tgz", + "integrity": "sha512-aS8KTA+nNhSNJJBlIhxgvU135WzoObwzFwav4wTDti/Gmhxqe0fs/Q343igo8Z7HGqPB/xgmoagwySZAlHmIfA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.8.1", + "@parcel/watcher": "^2.4.1", + "@swc/core": "^1.15.2", + "icu-minify": "^4.13.1", + "negotiator": "^1.0.0", + "next-intl-swc-plugin-extractor": "^4.13.1", + "po-parser": "^2.1.1", + "use-intl": "^4.13.1" + }, + "peerDependencies": { + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/next-intl-swc-plugin-extractor": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.13.1.tgz", + "integrity": "sha512-RhlH2DR1ViEXzcX7G3tDXAvzNrBL2Ph54Hq/q/9oP9eXQV/okh3UQpA/lx2k9U5Ck83CZOSgH0eFTNi5U+zyXw==", + "license": "MIT" + }, + "node_modules/next-intl/node_modules/@swc/core": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz", + "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.27" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.43", + "@swc/core-darwin-x64": "1.15.43", + "@swc/core-linux-arm-gnueabihf": "1.15.43", + "@swc/core-linux-arm64-gnu": "1.15.43", + "@swc/core-linux-arm64-musl": "1.15.43", + "@swc/core-linux-ppc64-gnu": "1.15.43", + "@swc/core-linux-s390x-gnu": "1.15.43", + "@swc/core-linux-x64-gnu": "1.15.43", + "@swc/core-linux-x64-musl": "1.15.43", + "@swc/core-win32-arm64-msvc": "1.15.43", + "@swc/core-win32-ia32-msvc": "1.15.43", + "@swc/core-win32-x64-msvc": "1.15.43" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, "node_modules/next-themes": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", @@ -12858,6 +13484,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -13587,6 +14219,12 @@ "node": ">=18" } }, + "node_modules/po-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz", + "integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==", + "license": "MIT" + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -16502,6 +17140,27 @@ } } }, + "node_modules/use-intl": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.13.1.tgz", + "integrity": "sha512-UU5C3zAC7yVg3m7rq5C8VF5J5jhAfvS19Wi9bPNCB9xB7jQYBsUcrqfdxs4Mxl9XR3x6BDB5K++iAw7/rcm3gg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^3.1.0", + "@schummar/icu-type-parser": "1.21.5", + "icu-minify": "^4.13.1", + "intl-messageformat": "^11.1.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", @@ -16597,6 +17256,7 @@ "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", diff --git a/web-ui/package.json b/web-ui/package.json index 602e4729..17a6eeb0 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -32,6 +32,7 @@ "dompurify": "^3.4.9", "lucide-react": "^0.562.0", "next": "^16.2.6", + "next-intl": "^4.13.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "^19.2.1", diff --git a/web-ui/src/app/(authenticated)/decks/page.tsx b/web-ui/src/app/(authenticated)/decks/page.tsx index 3a337bff..b474d828 100644 --- a/web-ui/src/app/(authenticated)/decks/page.tsx +++ b/web-ui/src/app/(authenticated)/decks/page.tsx @@ -34,9 +34,11 @@ import { useSwipe } from "@/hooks/useSwipe" import { useDeckList } from "@/hooks/useDeckList" import { useWorkspace } from "@/hooks/useWorkspace" import { Plus, MessageSquare, Image as ImageIcon, Star } from "lucide-react" +import { useTranslations } from "next-intl" import { IS_LOCAL } from "@/lib/mode" export default function DecksPage() { + const t = useTranslations("decksPage") const auth = useAuth() const isMobile = useIsMobile() const idToken = auth.user?.id_token @@ -90,7 +92,7 @@ export default function DecksPage() { }`} > - Chat + {t("chat")} @@ -154,7 +156,7 @@ export default function DecksPage() { ? "text-brand-amber" : "text-foreground-muted hover:text-brand-amber" }`} - aria-label={list.favoriteIds.has(ws.activeDeckId!) ? "Remove from favorites" : "Add to favorites"} + aria-label={list.favoriteIds.has(ws.activeDeckId!) ? t("removeFromFavorites") : t("addToFavorites")} > @@ -237,9 +239,9 @@ export default function DecksPage() { { if (!open) list.setDeleteTarget(null) }} - title="Delete this deck?" - description={<>{list.deleteTarget?.name} will be permanently deleted after 30 days. This action cannot be undone.} - confirmLabel="Delete" + title={t("deleteTitle")} + description={<>{t.rich("deleteDescription", { name: () => {list.deleteTarget?.name} })}} + confirmLabel={t("delete")} variant="destructive" onConfirm={list.confirmDelete} /> @@ -255,7 +257,7 @@ export default function DecksPage() { className="flex items-center gap-2 px-4 py-2.5 rounded-full bg-brand-teal text-primary-foreground shadow-lg text-sm font-medium animate-card-in" > - New Deck + {t("newDeck")} @@ -263,7 +265,7 @@ export default function DecksPage() {