From 1cf32705a72c44994c5ba06ad73d1303d6b4f023 Mon Sep 17 00:00:00 2001
From: yuta
Date: Mon, 28 Sep 2026 18:42:16 +0900
Subject: [PATCH] feat: translate system interface to English
---
README.md | 2 +
docs/execution-support.md | 12 +-
docs/vitals-preview.md | 2 +-
e2e/integrations/connections.test.ts | 32 ++--
e2e/integrations/deck.test.ts | 152 +++++++++---------
e2e/integrations/home.test.ts | 101 ++++++------
e2e/integrations/inbox.test.ts | 38 ++---
e2e/integrations/research.test.ts | 56 +++----
e2e/integrations/support.test.ts | 115 +++++++------
e2e/integrations/vitals.test.ts | 6 +-
e2e/integrations/webmcp.test.ts | 8 +-
e2e/integrations/workspace.test.ts | 12 +-
e2e/workspace.ts | 14 +-
src/components/app-shell.tsx | 18 +--
src/components/dialog.tsx | 2 +-
src/components/ui/sidebar.tsx | 6 +-
src/components/workspace-navigation.tsx | 16 +-
.../connections/connection-manager-dialog.tsx | 12 +-
.../connections/connection-manager.test.tsx | 38 ++---
.../connections/connection-manager.tsx | 48 +++---
src/features/connections/repository.server.ts | 8 +-
src/features/connections/repository.test.ts | 10 +-
src/features/decks/column-editor.test.tsx | 60 +++----
src/features/decks/column-editor.tsx | 20 +--
src/features/decks/deck-column.test.tsx | 8 +-
src/features/decks/deck-column.tsx | 42 +++--
src/features/decks/deck-page.tsx | 66 ++++----
src/features/decks/deck-switcher.tsx | 32 ++--
src/features/decks/import.server.ts | 4 +-
src/features/decks/import.test.ts | 4 +-
src/features/decks/legacy-import.ts | 4 +-
src/features/decks/mastodon-source-editor.tsx | 39 ++---
src/features/decks/model.ts | 11 +-
src/features/decks/repository.server.ts | 6 +-
src/features/decks/repository.test.ts | 18 +--
.../decks/research-post-card.test.tsx | 14 +-
src/features/decks/research-post-card.tsx | 16 +-
src/features/decks/server-functions.ts | 4 +-
src/features/decks/source-description.ts | 14 +-
src/features/decks/twitter-source-editor.tsx | 45 +++---
src/features/decks/use-deck.test.ts | 2 +-
src/features/decks/use-deck.ts | 20 ++-
src/features/home/daily-brief.tsx | 20 +--
src/features/home/home-page.tsx | 97 +++++------
src/features/home/interpret-action.test.ts | 55 ++++++-
src/features/home/interpret-action.ts | 80 ++++++---
src/features/inbox/inbox-page.tsx | 16 +-
src/features/inbox/mock-articles.ts | 128 +++++++--------
src/features/journal/journal-page.tsx | 82 +++++-----
.../platforms/mastodon-feed.server.ts | 10 +-
.../platforms/mastodon-server-functions.ts | 2 +-
src/features/posts/inputs.ts | 26 +--
src/features/posts/post-service.test.ts | 12 +-
src/features/posts/post-service.ts | 23 ++-
src/features/posts/server-functions.ts | 2 +-
.../profiles/profile-service.server.ts | 4 +-
src/features/profiles/profile-service.test.ts | 2 +-
src/features/research/repository.server.ts | 3 +-
src/features/research/repository.test.ts | 4 +-
.../research/research-deck-sync.test.ts | 2 +-
src/features/research/research-deck-sync.ts | 2 +-
src/features/research/research-markdown.tsx | 6 +-
src/features/research/research-panel.test.tsx | 136 ++++++++--------
src/features/research/research-panel.tsx | 87 +++++-----
src/features/research/runner.server.ts | 52 +++---
src/features/research/runner.test.ts | 33 ++--
.../research/use-citation-navigation.test.ts | 2 +-
.../research/use-citation-navigation.ts | 2 +-
src/features/support/conversation-detail.tsx | 50 +++---
src/features/support/mock-conversations.ts | 152 ++++++++++--------
src/features/support/support-page.tsx | 40 ++---
src/features/vitals/vitals-page.tsx | 13 +-
src/features/workspace/workspace-state.tsx | 15 +-
src/routes/__root.tsx | 2 +-
74 files changed, 1232 insertions(+), 1065 deletions(-)
diff --git a/README.md b/README.md
index b4f9d1f..bce796a 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,8 @@ bound to a connection account, so platforms and multiple accounts work side by s
- Saved chat history with deck, account and citation restoration across devices
and backend restarts
+The system interface is in English; user-entered text and external posts retain their original language.
+
`/` opens the daily home with a greeting, natural-language action proposals,
flexible tasks/routines, messages, notes, and reading. `/deck` opens Twitter Lite.
The shared sidebar also opens
diff --git a/docs/execution-support.md b/docs/execution-support.md
index 11a3af7..dbb085f 100644
--- a/docs/execution-support.md
+++ b/docs/execution-support.md
@@ -1,7 +1,7 @@
# Execution support prototype
-The shared sidebar opens `/support` (連絡) and `/journal` (記録), alongside
-リーダー and Twitter Lite. These are UI-only prototypes with fictional contacts
+The shared sidebar opens `/support` (Messages) and `/journal` (Journal), alongside
+Reader and Twitter Lite. These are UI-only prototypes with fictional contacts
and in-memory state. Reloading resets the prototype state. Tasks, communication status, outcomes, and
journal entries are shared between routes for the current browser session.
No messages, calendar events, medical records, or Rensheng pages are written.
@@ -37,21 +37,21 @@ the new communication and journal prototypes do not consume those accounts.
## Daily home
`/` presents a greeting, a large input, and a flexible plan grouped as
-「まずこれ」「いつものこと」「余裕があれば」. There are no fixed time slots.
+Start here, Routines, and If there’s room. There are no fixed time slots.
Tasks can be reordered, completed, deferred, restored, or followed as routine
steps. Communications can be reviewed directly in a home dialog; changes also
appear in `/support`. Journal entries and corrections appear on the home, and
reader samples open for reading. Twitter Lite remains a separate work surface.
-The input currently uses a deterministic Japanese mock interpreter, not an LLM.
+The input currently uses a deterministic English/Japanese mock interpreter, not an LLM.
It proposes typed actions for task creation/status/reordering, routine steps,
contact reply/review or deferral, notes/weight/medication records, and navigation.
Confirming a proposal dispatches the corresponding operation. Unknown or
ambiguous input does not change state. Message sending still goes through the
existing simulated review UI; this prototype performs no external send.
-Examples: 「買い物を追加」「朝の支度をあとで」「青木さんに返信」
-「メモ:次は資料の2ページ目から」「体重は65.2kg」「リーダーを開く」.
+Examples: `Add grocery shopping`, `Defer Morning routine`, `Reply to Aoki`,
+`Note: Resume from page 2`, `Weight: 65.2 kg`, and `Open Reader`.
The interaction reference is [Tiimo visual planning](https://www.tiimoapp.com/product/visual-planning),
reviewed on 2026-09-28: combined routines and tasks, flexible reordering, small
diff --git a/docs/vitals-preview.md b/docs/vitals-preview.md
index 24b0aa2..6515db4 100644
--- a/docs/vitals-preview.md
+++ b/docs/vitals-preview.md
@@ -2,7 +2,7 @@
`/vitals` recreates the Body and Environment screenshots attached to
[Alan Shiflett’s post](https://x.com/AlanShiflett/status/2103524300158366183).
-The shared sidebar opens it as バイタル. Body categories and history, environment
+The shared sidebar opens it as Vitals. Body categories and history, environment
metric details, chart period/series selection, and simulated climate controls
are interactive. Other screenshot navigation entries open a preview explanation.
diff --git a/e2e/integrations/connections.test.ts b/e2e/integrations/connections.test.ts
index 876fffa..b37234a 100644
--- a/e2e/integrations/connections.test.ts
+++ b/e2e/integrations/connections.test.ts
@@ -5,21 +5,21 @@ test('opens account management after hydration without inheriting real OAuth con
}) => {
await page.goto('/deck')
await expect(
- page.getByRole('button', { name: '名前を変更', includeHidden: true }),
+ page.getByRole('button', { name: 'Rename', includeHidden: true }),
).toBeEnabled()
- const manage = page.getByRole('button', { name: '接続アカウントを管理' })
+ const manage = page.getByRole('button', { name: 'Manage connected accounts' })
if (!(await manage.isVisible())) {
- await page.getByRole('button', { name: 'サイドバーを切り替え' }).click()
+ await page.getByRole('button', { name: 'Toggle sidebar' }).click()
}
await expect(manage).toBeEnabled()
await manage.click()
- const dialog = page.getByRole('dialog', { name: '接続アカウント' })
+ const dialog = page.getByRole('dialog', { name: 'Connected accounts' })
await expect(dialog).toBeVisible()
await expect(
- dialog.getByText('利用するMastodonサーバーがまだ設定されていません。'),
+ dialog.getByText('No Mastodon servers have been configured yet.'),
).toBeVisible()
await expect(
- dialog.getByRole('button', { name: 'Mastodonで認可する' }),
+ dialog.getByRole('button', { name: 'Authorize with Mastodon' }),
).toHaveCount(0)
await page.keyboard.press('Escape')
await expect(dialog).toHaveCount(0)
@@ -32,19 +32,21 @@ test('opens account management from the inbox footer and restores focus on close
}) => {
await page.goto('/inbox')
await page.waitForLoadState('networkidle')
- const manage = page.getByRole('button', { name: '接続アカウントを管理' })
+ const manage = page.getByRole('button', { name: 'Manage connected accounts' })
await expect(manage).toBeVisible()
await manage.click()
- const dialog = page.getByRole('dialog', { name: '接続アカウント' })
+ const dialog = page.getByRole('dialog', { name: 'Connected accounts' })
await expect(dialog).toBeVisible()
await expect(
- dialog.getByText('利用するMastodonサーバーがまだ設定されていません。'),
+ dialog.getByText('No Mastodon servers have been configured yet.'),
).toBeVisible()
expect((await a11y().analyze()).violations).toEqual([])
await page.keyboard.press('Escape')
await expect(dialog).toHaveCount(0)
await expect(manage).toBeFocused()
- await expect(page.getByRole('textbox', { name: '記事を検索' })).toBeVisible()
+ await expect(
+ page.getByRole('textbox', { name: 'Search articles' }),
+ ).toBeVisible()
})
test('rejects an unbound OAuth callback without contacting an instance', async ({
@@ -53,16 +55,16 @@ test('rejects an unbound OAuth callback without contacting an instance', async (
await page.goto('/oauth/mastodon/callback?state=invalid&code=not-a-real-code')
await expect(page).toHaveURL(/\/deck\?mastodon=failed$/)
await expect(page.getByRole('alert')).toContainText(
- 'Mastodonの認可を完了できませんでした。',
+ 'Could not complete Mastodon authorization.',
)
await expect(
- page.getByRole('button', { name: '名前を変更', includeHidden: true }),
+ page.getByRole('button', { name: 'Rename', includeHidden: true }),
).toBeEnabled()
- const manage = page.getByRole('button', { name: '接続アカウントを管理' })
+ const manage = page.getByRole('button', { name: 'Manage connected accounts' })
if (!(await manage.isVisible())) {
- await page.getByRole('button', { name: 'サイドバーを切り替え' }).click()
+ await page.getByRole('button', { name: 'Toggle sidebar' }).click()
}
await expect(
- page.getByRole('button', { name: '接続アカウントを管理' }),
+ page.getByRole('button', { name: 'Manage connected accounts' }),
).toBeEnabled()
})
diff --git a/e2e/integrations/deck.test.ts b/e2e/integrations/deck.test.ts
index 8060cc4..d025b33 100644
--- a/e2e/integrations/deck.test.ts
+++ b/e2e/integrations/deck.test.ts
@@ -10,26 +10,26 @@ async function addColumn(
kind = 'search',
) {
await showDeckManagement(page)
- await page.getByRole('button', { name: 'カラムを追加', exact: true }).click()
- const editor = page.getByRole('form', { name: 'カラムを追加', exact: true })
- await editor.getByLabel('カラム名', { exact: true }).fill(title)
+ await page.getByRole('button', { name: 'Add column', exact: true }).click()
+ const editor = page.getByRole('form', { name: 'Add column', exact: true })
+ await editor.getByLabel('Column name', { exact: true }).fill(title)
await editor
- .getByRole('combobox', { name: '接続プロファイル', exact: true })
+ .getByRole('combobox', { name: 'Connection profile', exact: true })
.selectOption({ label: profile })
await editor
- .getByRole('combobox', { name: 'カラムの種類', exact: true })
+ .getByRole('combobox', { name: 'Column type', exact: true })
.selectOption(kind)
await editor
.getByLabel(
kind === 'search'
- ? 'Twitterの検索条件'
+ ? 'Twitter search query'
: kind === 'user'
- ? 'ユーザー'
- : 'リスト',
+ ? 'User'
+ : 'List',
{ exact: true },
)
.fill(query)
- await editor.getByRole('button', { name: '追加して検索' }).click()
+ await editor.getByRole('button', { name: 'Add and search' }).click()
await closeMobileSidebar(page)
await expect(
page
@@ -42,13 +42,13 @@ async function addColumn(
test.beforeEach(async ({ page }) => {
await page.goto('/deck')
await expect(
- page.getByRole('button', { name: 'デッキとして保存', exact: true }),
+ page.getByRole('button', { name: 'Save as deck', exact: true }),
).toBeEnabled()
- await page
- .getByRole('button', { name: 'デッキとして保存', exact: true })
- .click()
+ await page.getByRole('button', { name: 'Save as deck', exact: true }).click()
await showDeckManagement(page)
- await expect(page.getByText('サーバーに保存・端末間で共有')).toBeVisible()
+ await expect(
+ page.getByText('Saved to server · Shared across devices'),
+ ).toBeVisible()
await closeMobileSidebar(page)
})
@@ -65,19 +65,19 @@ test('binds identical searches to independent profiles through paging, editing a
await expect(
second.getByText('alternate · Latest · all page 1', { exact: true }),
).toBeVisible()
- await first.getByRole('button', { name: '続きを読み込む' }).click()
+ await first.getByRole('button', { name: 'Load more' }).click()
await expect(first.locator('article')).toHaveCount(2)
await expect(second.locator('article')).toHaveCount(1)
- await second.getByRole('button', { name: '続きを読み込む' }).click()
+ await second.getByRole('button', { name: 'Load more' }).click()
await expect(
second.getByText('alternate · Latest · all page 2'),
).toBeVisible()
- await second.getByRole('button', { name: '別の接続の操作' }).click()
- await page.getByRole('menuitem', { name: '編集', exact: true }).click()
+ await second.getByRole('button', { name: 'Actions for 別の接続' }).click()
+ await page.getByRole('menuitem', { name: 'Edit', exact: true }).click()
await page
- .getByRole('combobox', { name: '接続プロファイル', exact: true })
+ .getByRole('combobox', { name: 'Connection profile', exact: true })
.selectOption({ label: 'e2e' })
- await page.getByRole('button', { name: '変更を保存' }).click()
+ await page.getByRole('button', { name: 'Save changes' }).click()
await expect(
second.getByText('Latest · all page 1', { exact: true }),
).toBeVisible()
@@ -94,19 +94,19 @@ test('binds identical searches to independent profiles through paging, editing a
test('creates, switches, renames and deletes persisted deck profiles', async ({
page,
}) => {
- await page.getByRole('button', { name: '名前を変更' }).click()
- await page.getByLabel('調査テーマ・デッキ名').fill('最初の調査')
- await page.getByRole('button', { name: '名前を保存' }).click()
+ await page.getByRole('button', { name: 'Rename' }).click()
+ await page.getByLabel('Research topic / deck name').fill('最初の調査')
+ await page.getByRole('button', { name: 'Save name' }).click()
await addColumn(page, '最初の観点', 'first-deck')
await showDeckManagement(page)
- await page.getByRole('button', { name: 'デッキを作成', exact: true }).click()
- await page.getByLabel('新しいデッキ名').fill('別の調査')
- await page.getByRole('button', { name: '作成', exact: true }).click()
+ await page.getByRole('button', { name: 'Create deck', exact: true }).click()
+ await page.getByLabel('New deck name').fill('別の調査')
+ await page.getByRole('button', { name: 'Create', exact: true }).click()
await expect(page.locator('.deck-column')).toHaveCount(0)
await addColumn(page, '別の観点', 'second-deck', 'alternate')
await showDeckManagement(page)
await page
- .getByLabel('デッキプロファイル', { exact: true })
+ .getByLabel('Deck profile', { exact: true })
.selectOption({ label: '最初の調査' })
await closeMobileSidebar(page)
await expect(page.locator('.deck-column h2')).toHaveText(['最初の観点'])
@@ -114,18 +114,18 @@ test('creates, switches, renames and deletes persisted deck profiles', async ({
await expect(page.getByRole('heading', { level: 1 })).toHaveText('最初の調査')
await showDeckManagement(page)
await page
- .getByLabel('デッキプロファイル', { exact: true })
+ .getByLabel('Deck profile', { exact: true })
.selectOption({ label: '別の調査' })
await closeMobileSidebar(page)
await expect(page.locator('.deck-column h2')).toHaveText(['別の観点'])
await showDeckManagement(page)
- await page.getByRole('button', { name: 'デッキを削除', exact: true }).click()
- await page.getByRole('button', { name: '削除する', exact: true }).click()
+ await page.getByRole('button', { name: 'Delete deck', exact: true }).click()
+ await page.getByRole('button', { name: 'Delete', exact: true }).click()
await page.reload()
await expect(page.getByRole('heading', { level: 1 })).toHaveText('最初の調査')
await showDeckManagement(page)
await expect(
- page.getByLabel('デッキプロファイル', { exact: true }).locator('option'),
+ page.getByLabel('Deck profile', { exact: true }).locator('option'),
).toHaveCount(1)
})
@@ -138,17 +138,17 @@ test('reads user and list timelines within columns and reorders, deletes and res
const list = page.getByRole('region', { name: 'リストの投稿', exact: true })
await expect(user.getByText('alternate profile page 1')).toBeVisible()
await expect(list.getByText('list page 1')).toBeVisible()
- await list.getByRole('button', { name: '続きを読み込む' }).click()
+ await list.getByRole('button', { name: 'Load more' }).click()
await expect(list.getByText('list page 2')).toBeVisible()
- await list.getByRole('button', { name: 'リストの投稿の操作' }).click()
- await page.getByRole('menuitem', { name: 'リストの投稿を左へ' }).click()
+ await list.getByRole('button', { name: 'Actions for リストの投稿' }).click()
+ await page.getByRole('menuitem', { name: 'Move リストの投稿 left' }).click()
await expect(page.locator('.deck-column h2')).toHaveText([
'リストの投稿',
'アカウント',
])
- await list.getByRole('button', { name: 'リストの投稿の操作' }).click()
- await page.getByRole('menuitem', { name: 'リストの投稿を削除' }).click()
- await page.getByRole('button', { name: '元に戻す' }).click()
+ await list.getByRole('button', { name: 'Actions for リストの投稿' }).click()
+ await page.getByRole('menuitem', { name: 'Delete リストの投稿' }).click()
+ await page.getByRole('button', { name: 'Undo' }).click()
await expect(page.locator('.deck-column h2')).toHaveText([
'リストの投稿',
'アカウント',
@@ -165,12 +165,12 @@ test('keeps other columns usable when pagination fails and retries that column',
await addColumn(page, '別の検索', 'independent-deck')
const failed = page.getByRole('region', { name: '失敗する検索', exact: true })
const other = page.getByRole('region', { name: '別の検索', exact: true })
- await failed.getByRole('button', { name: '続きを読み込む' }).click()
+ await failed.getByRole('button', { name: 'Load more' }).click()
await expect(failed.getByRole('alert')).toBeVisible()
await expect(failed.locator('article')).toHaveCount(1)
- await other.getByRole('button', { name: '続きを読み込む' }).click()
+ await other.getByRole('button', { name: 'Load more' }).click()
await expect(other.locator('article')).toHaveCount(2)
- await failed.getByRole('button', { name: '再試行' }).click()
+ await failed.getByRole('button', { name: 'Retry' }).click()
await expect(failed.locator('article')).toHaveCount(2)
})
@@ -201,9 +201,7 @@ test('keeps the deck and editor accessible within narrow viewports', async ({
width: testInfo.project.name === 'desktop' ? 1440 : 393,
height: 900,
})
- await expect(
- page.getByRole('navigation', { name: 'カラム一覧' }),
- ).toHaveCount(0)
+ await expect(page.getByRole('navigation', { name: 'Columns' })).toHaveCount(0)
await expect(
page.getByRole('heading', { name: '公式アカウント', exact: true }),
).toBeVisible()
@@ -213,7 +211,7 @@ test('keeps the deck and editor accessible within narrow viewports', async ({
fullPage: true,
})
await showDeckManagement(page)
- await page.getByRole('button', { name: 'カラムを追加', exact: true }).click()
+ await page.getByRole('button', { name: 'Add column', exact: true }).click()
expect((await a11y().analyze()).violations).toEqual([])
await page.screenshot({
path: testInfo.outputPath('deck.png'),
@@ -225,20 +223,22 @@ test('closes the column editor with Escape and restores focus without applying e
page,
}) => {
await showDeckManagement(page)
- const opener = page.getByRole('button', { name: 'カラムを追加', exact: true })
+ const opener = page.getByRole('button', { name: 'Add column', exact: true })
await opener.click()
await expect(
- page.getByRole('dialog', { name: 'カラムを追加', exact: true }),
+ page.getByRole('dialog', { name: 'Add column', exact: true }),
).toBeVisible()
- await page.getByLabel('カラム名', { exact: true }).fill('キャンセルする観点')
+ await page
+ .getByLabel('Column name', { exact: true })
+ .fill('キャンセルする観点')
await page.keyboard.press('Escape')
await expect(
- page.getByRole('dialog', { name: 'カラムを追加', exact: true }),
+ page.getByRole('dialog', { name: 'Add column', exact: true }),
).toHaveCount(0)
await expect(opener).toBeFocused()
await expect(page.locator('.deck-column')).toHaveCount(0)
await opener.click()
- await expect(page.getByLabel('カラム名', { exact: true })).toHaveValue('')
+ await expect(page.getByLabel('Column name', { exact: true })).toHaveValue('')
})
test('does not overwrite unreadable storage on reload', async ({ page }) => {
@@ -246,7 +246,7 @@ test('does not overwrite unreadable storage on reload', async ({ page }) => {
localStorage.setItem('twitter-lite-research-deck', '{broken'),
)
await page.reload()
- await expect(page.getByRole('alert')).toContainText('読み込めませんでした')
+ await expect(page.getByRole('alert')).toContainText('Unable to load')
expect(
await page.evaluate(() =>
localStorage.getItem('twitter-lite-research-deck'),
@@ -258,19 +258,19 @@ test('selects a list from the column profile catalog and refreshes it on profile
page,
}) => {
await showDeckManagement(page)
- await page.getByRole('button', { name: 'カラムを追加', exact: true }).click()
- await page.getByLabel('カラム名', { exact: true }).fill('購読リスト')
+ await page.getByRole('button', { name: 'Add column', exact: true }).click()
+ await page.getByLabel('Column name', { exact: true }).fill('購読リスト')
await page
- .getByRole('combobox', { name: '接続プロファイル', exact: true })
+ .getByRole('combobox', { name: 'Connection profile', exact: true })
.selectOption({ label: 'e2e' })
await page
- .getByRole('combobox', { name: 'カラムの種類', exact: true })
+ .getByRole('combobox', { name: 'Column type', exact: true })
.selectOption('list')
await expect(
page.getByRole('button', { name: 'My E2E List', exact: true }),
).toBeVisible()
await page
- .getByRole('combobox', { name: '接続プロファイル', exact: true })
+ .getByRole('combobox', { name: 'Connection profile', exact: true })
.selectOption({ label: 'alternate' })
await expect(
page.getByRole('button', { name: 'My E2E List', exact: true }),
@@ -278,10 +278,10 @@ test('selects a list from the column profile catalog and refreshes it on profile
await page
.getByRole('button', { name: 'Alternate List', exact: true })
.click()
- await expect(page.getByLabel('リスト', { exact: true })).toHaveValue(
+ await expect(page.getByLabel('List', { exact: true })).toHaveValue(
'1234567890',
)
- await page.getByRole('button', { name: '追加して検索' }).click()
+ await page.getByRole('button', { name: 'Add and search' }).click()
await closeMobileSidebar(page)
await expect(
page
@@ -302,27 +302,27 @@ test('keeps an open draft through remote edits and rejects its stale save', asyn
try {
const device = await other.newPage()
await device.goto('/deck')
- await page.getByRole('button', { name: '名前を変更' }).click()
- await page.getByLabel('調査テーマ・デッキ名').fill('編集中の名前')
- await device.getByRole('button', { name: '名前を変更' }).click()
- await device.getByLabel('調査テーマ・デッキ名').fill('別の端末の変更')
- await device.getByRole('button', { name: '名前を保存' }).click()
+ await page.getByRole('button', { name: 'Rename' }).click()
+ await page.getByLabel('Research topic / deck name').fill('編集中の名前')
+ await device.getByRole('button', { name: 'Rename' }).click()
+ await device.getByLabel('Research topic / deck name').fill('別の端末の変更')
+ await device.getByRole('button', { name: 'Save name' }).click()
await expect(device.getByRole('heading', { level: 1 })).toHaveText(
'別の端末の変更',
)
await page.evaluate(() => window.dispatchEvent(new Event('focus')))
- await expect(page.getByLabel('調査テーマ・デッキ名')).toHaveValue(
+ await expect(page.getByLabel('Research topic / deck name')).toHaveValue(
'編集中の名前',
)
- await page.getByRole('button', { name: '名前を保存' }).click()
+ await page.getByRole('button', { name: 'Save name' }).click()
await expect(page.getByRole('alert')).toContainText(
- '別の端末で変更されました',
+ 'This deck changed on another device',
)
- await expect(page.getByLabel('調査テーマ・デッキ名')).toHaveValue(
+ await expect(page.getByLabel('Research topic / deck name')).toHaveValue(
'編集中の名前',
)
await page.keyboard.press('Escape')
- await page.getByRole('button', { name: '最新のデッキを再読み込み' }).click()
+ await page.getByRole('button', { name: 'Reload latest deck' }).click()
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
'別の端末の変更',
)
@@ -368,10 +368,10 @@ test('imports old browser decks explicitly and avoids duplicate imports after re
)
await page.reload()
await showDeckManagement(page)
- const selector = page.getByLabel('デッキプロファイル', { exact: true })
+ const selector = page.getByLabel('Deck profile', { exact: true })
await expect(selector.locator('option')).toHaveCount(1)
await closeMobileSidebar(page)
- await page.getByRole('button', { name: '旧デッキを取り込む' }).click()
+ await page.getByRole('button', { name: 'Import legacy decks' }).click()
await showDeckManagement(page)
await expect(selector.locator('option')).toHaveCount(2)
await selector.selectOption({ label: '以前の調査' })
@@ -388,9 +388,9 @@ test('imports old browser decks explicitly and avoids duplicate imports after re
legacy,
)
await page.reload()
- await page.getByRole('button', { name: '旧デッキを取り込む' }).click()
+ await page.getByRole('button', { name: 'Import legacy decks' }).click()
await expect(
- page.getByRole('button', { name: '旧デッキを取り込む' }),
+ page.getByRole('button', { name: 'Import legacy decks' }),
).toHaveCount(0)
await showDeckManagement(page)
await expect(selector.locator('option')).toHaveCount(2)
@@ -400,19 +400,19 @@ test('preserves a new deck dialog and its draft across the mobile breakpoint', a
page,
}) => {
await showDeckManagement(page)
- await page.getByRole('button', { name: 'デッキを作成', exact: true }).click()
- const dialog = page.getByRole('dialog', { name: 'デッキを作成', exact: true })
- await dialog.getByLabel('新しいデッキ名').fill('画面を回転しても残る調査')
+ await page.getByRole('button', { name: 'Create deck', exact: true }).click()
+ const dialog = page.getByRole('dialog', { name: 'Create deck', exact: true })
+ await dialog.getByLabel('New deck name').fill('画面を回転しても残る調査')
for (const width of [1024, 375, 1024]) {
await page.setViewportSize({ width, height: 900 })
await expect(dialog).toHaveCount(1)
await expect(dialog).toBeVisible()
- await expect(dialog.getByLabel('新しいデッキ名')).toHaveValue(
+ await expect(dialog.getByLabel('New deck name')).toHaveValue(
'画面を回転しても残る調査',
)
await expect(page.locator('#new-deck-title')).toHaveCount(1)
}
- await dialog.getByRole('button', { name: '作成', exact: true }).click()
+ await dialog.getByRole('button', { name: 'Create', exact: true }).click()
await expect(dialog).toHaveCount(0)
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
'画面を回転しても残る調査',
diff --git a/e2e/integrations/home.test.ts b/e2e/integrations/home.test.ts
index e7c185d..610abaf 100644
--- a/e2e/integrations/home.test.ts
+++ b/e2e/integrations/home.test.ts
@@ -6,21 +6,17 @@ test('previews a natural-language memo and shares the executed record with journ
await page.goto('/')
await page.waitForLoadState('networkidle')
const memo = '明日のレビューは導線から確認する'
- await page
- .getByRole('textbox', { name: 'ホームへの入力' })
- .fill(`メモ: ${memo}`)
- await page.getByRole('button', { name: '候補を表示' }).click()
- const execute = page.getByRole('button', { name: '実行する', exact: true })
+ await page.getByRole('textbox', { name: 'Home input' }).fill(`Note: ${memo}`)
+ await page.getByRole('button', { name: 'Preview action' }).click()
+ const execute = page.getByRole('button', { name: 'Apply', exact: true })
await expect(execute).toBeVisible()
await execute.click()
- await page.getByRole('link', { name: '記録', exact: true }).first().click()
- await expect(page.getByRole('article', { name: 'メモの記録' })).toContainText(
+ await page.getByRole('link', { name: 'Journal', exact: true }).first().click()
+ await expect(page.getByRole('article', { name: 'Note entry' })).toContainText(
memo,
)
- await page.getByRole('link', { name: 'ホーム', exact: true }).first().click()
- await expect(
- page.getByRole('textbox', { name: 'ホームへの入力' }),
- ).toBeVisible()
+ await page.getByRole('link', { name: 'Home', exact: true }).first().click()
+ await expect(page.getByRole('textbox', { name: 'Home input' })).toBeVisible()
})
test('keeps the home usable on narrow screens', async ({
@@ -32,7 +28,7 @@ test('keeps the home usable on narrow screens', async ({
for (const width of [320, 375, 768]) {
await page.setViewportSize({ width, height: 850 })
await expect(
- page.getByRole('textbox', { name: 'ホームへの入力' }),
+ page.getByRole('textbox', { name: 'Home input' }),
).toBeVisible()
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
@@ -47,28 +43,30 @@ test('adds tasks only after confirmation and can defer and restore them', async
}) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
- const input = page.getByRole('textbox', { name: 'ホームへの入力' })
+ const input = page.getByRole('textbox', { name: 'Home input' })
const complete = page.getByRole('button', {
- name: '買い物を完了',
+ name: 'Complete grocery shopping',
exact: true,
})
- await input.fill('買い物を追加')
- await page.getByRole('button', { name: '候補を表示' }).click()
+ await input.fill('Add grocery shopping')
+ await page.getByRole('button', { name: 'Preview action' }).click()
await expect(complete).toHaveCount(0)
- await page.getByRole('button', { name: '実行する', exact: true }).click()
+ await page.getByRole('button', { name: 'Apply', exact: true }).click()
await expect(complete).toBeVisible()
- await input.fill('買い物をあとで')
- await page.getByRole('button', { name: '候補を表示' }).click()
+ await input.fill('Defer grocery shopping')
+ await page.getByRole('button', { name: 'Preview action' }).click()
await expect(complete).toBeVisible()
- await page.getByRole('button', { name: '実行する', exact: true }).click()
+ await page.getByRole('button', { name: 'Apply', exact: true }).click()
await expect(complete).toHaveCount(0)
await page.locator('.home-rest summary').click()
- await page.getByRole('button', { name: '買い物を戻す', exact: true }).click()
+ await page
+ .getByRole('button', { name: 'Restore grocery shopping', exact: true })
+ .click()
await complete.click()
await expect(complete).toHaveCount(0)
await page.locator('.home-rest summary').click()
await expect(
- page.getByRole('button', { name: '買い物を戻す', exact: true }),
+ page.getByRole('button', { name: 'Restore grocery shopping', exact: true }),
).toBeVisible()
})
@@ -78,30 +76,37 @@ test('resolves communication within home and carries the status to the shared qu
}) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
- await page.getByRole('button', { name: '青木 遥との会話を開く' }).click()
- const dialog = page.getByRole('dialog', { name: '青木 遥との連絡' })
+ await page
+ .getByRole('button', { name: 'Open conversation with Haruka Aoki' })
+ .click()
+ const dialog = page.getByRole('dialog', {
+ name: 'Conversation with Haruka Aoki',
+ })
await expect(
- dialog.getByRole('textbox', { name: '返信', exact: true }),
+ dialog.getByRole('textbox', { name: 'Reply', exact: true }),
).toBeVisible()
expect((await a11y().analyze()).violations).toEqual([])
- await dialog.getByRole('button', { name: '今は置いておく' }).click()
+ await dialog.getByRole('button', { name: 'Leave for later' }).click()
await expect(dialog).toHaveCount(0)
await expect(
- page.getByRole('button', { name: '青木 遥との会話を開く' }),
+ page.getByRole('button', { name: 'Open conversation with Haruka Aoki' }),
).toHaveCount(0)
- await page.getByRole('link', { name: '連絡', exact: true }).first().click()
+ await page
+ .getByRole('link', { name: 'Messages', exact: true })
+ .first()
+ .click()
const rows = page.locator('.support-row')
await expect(
- rows.filter({ hasText: '来週のレビュー、日程を決める' }),
+ rows.filter({ hasText: 'Schedule next week’s design review' }),
).toHaveCount(0)
- await page.getByRole('button', { name: 'あとで', exact: true }).click()
- await rows.filter({ hasText: '来週のレビュー、日程を決める' }).click()
- await page.getByRole('button', { name: '今に戻す' }).click()
- const back = page.getByRole('button', { name: '連絡一覧に戻る' })
+ await page.getByRole('button', { name: 'Later', exact: true }).click()
+ await rows.filter({ hasText: 'Schedule next week’s design review' }).click()
+ await page.getByRole('button', { name: 'Move to Now' }).click()
+ const back = page.getByRole('button', { name: 'Back to messages' })
if (await back.isVisible()) await back.click()
- await page.getByRole('link', { name: 'ホーム', exact: true }).first().click()
+ await page.getByRole('link', { name: 'Home', exact: true }).first().click()
await expect(
- page.getByRole('button', { name: '青木 遥との会話を開く' }),
+ page.getByRole('button', { name: 'Open conversation with Haruka Aoki' }),
).toBeVisible()
})
@@ -110,25 +115,25 @@ test('updates the prose brief as priorities change and opens the referenced conv
}) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
- const brief = page.getByRole('region', { name: '今日のブリーフ' })
- await expect(brief).toContainText('レビュー資料を確認する')
- await expect(brief).toContainText('4件')
+ const brief = page.getByRole('region', { name: 'Daily brief' })
+ await expect(brief).toContainText('Review the documents')
+ await expect(brief).toContainText('4 conversations')
await page
- .getByRole('button', { name: 'レビュー資料を確認するを完了', exact: true })
+ .getByRole('button', { name: 'Complete Review the documents', exact: true })
.click()
- await expect(brief).not.toContainText('レビュー資料を確認する')
- await brief.getByRole('button', { name: '青木 遥さんへの返信' }).click()
+ await expect(brief).not.toContainText('Review the documents')
+ await brief.getByRole('button', { name: 'Reply to Haruka Aoki' }).click()
const dialog = page.getByRole('dialog', {
- name: '青木 遥との連絡',
+ name: 'Conversation with Haruka Aoki',
exact: true,
})
- await dialog.getByRole('button', { name: '今は置いておく' }).click()
- await expect(brief).toContainText('3件')
- await expect(brief).not.toContainText('青木 遥')
+ await dialog.getByRole('button', { name: 'Leave for later' }).click()
+ await expect(brief).toContainText('3 conversations')
+ await expect(brief).not.toContainText('Haruka Aoki')
await page
- .getByRole('textbox', { name: 'ホームへの入力' })
+ .getByRole('textbox', { name: 'Home input' })
.fill('再開地点:資料の2ページ目から')
- await page.getByRole('button', { name: '候補を表示' }).click()
- await page.getByRole('button', { name: '実行する', exact: true }).click()
+ await page.getByRole('button', { name: 'Preview action' }).click()
+ await page.getByRole('button', { name: 'Apply', exact: true }).click()
await expect(brief).toContainText('資料の2ページ目から')
})
diff --git a/e2e/integrations/inbox.test.ts b/e2e/integrations/inbox.test.ts
index 4670732..9e8fc7d 100644
--- a/e2e/integrations/inbox.test.ts
+++ b/e2e/integrations/inbox.test.ts
@@ -7,7 +7,7 @@ test('selects articles and keeps the reader focused on their content', async ({
await page.waitForLoadState('networkidle')
const rows = page.locator('.inbox-article-row')
await expect(rows).toHaveCount(9)
- const articleTitle = 'エージェントに渡す文脈は、短くてもよい'
+ const articleTitle = 'Context for an agent can be brief'
await rows.filter({ hasText: articleTitle }).click()
const reader = page.getByRole('article', { name: articleTitle, exact: true })
await expect(reader).toBeVisible()
@@ -16,26 +16,26 @@ test('selects articles and keeps the reader focused on their content', async ({
).toBeVisible()
await expect(
reader.getByText(
- 'まず目的を一文で書き、次に変えてはいけない条件を添える。',
+ 'Start with a one-sentence goal, then add the constraints that must hold.',
{ exact: false },
),
).toBeVisible()
await expect(page.getByText('FOR YOU', { exact: true })).toHaveCount(0)
- await expect(page.getByText('モック', { exact: true })).toHaveCount(0)
+ await expect(page.getByText('Mock', { exact: true })).toHaveCount(0)
+ await expect(page.getByText('Why this article', { exact: true })).toHaveCount(
+ 0,
+ )
await expect(
- page.getByText('この記事が届いた理由', { exact: true }),
+ page.getByRole('button', { name: /For you|Read later|Unread only/ }),
).toHaveCount(0)
- await expect(
- page.getByRole('button', { name: /あなたへ|あとで読む|未読のみ/ }),
- ).toHaveCount(0)
- const back = page.getByRole('button', { name: '記事一覧に戻る' })
+ const back = page.getByRole('button', { name: 'Back to articles' })
if (await back.isVisible()) await back.click()
await rows
- .filter({ hasText: '3カラムの余白が、情報を読むリズムをつくる' })
+ .filter({ hasText: 'Three columns create a rhythm for reading' })
.click()
await expect(
page.getByRole('article', {
- name: '3カラムの余白が、情報を読むリズムをつくる',
+ name: 'Three columns create a rhythm for reading',
exact: true,
}),
).toBeVisible()
@@ -49,21 +49,21 @@ test('recovers from empty search and shares navigation with Twitter Lite', async
await page.waitForLoadState('networkidle')
const rows = page.locator('.inbox-article-row')
await page
- .getByRole('textbox', { name: '記事を検索' })
- .fill('存在しない検索語xyz')
+ .getByRole('textbox', { name: 'Search articles' })
+ .fill('nonexistent-query-xyz')
await expect(rows).toHaveCount(0)
await expect(
- page.getByText('記事がありません', { exact: true }),
+ page.getByText('No articles found', { exact: true }),
).toBeVisible()
- await page.getByRole('button', { name: '検索をクリア' }).click()
+ await page.getByRole('button', { name: 'Clear search' }).click()
await expect(rows).toHaveCount(9)
expect((await a11y().analyze()).violations).toEqual([])
await expect(
- page.getByRole('button', { name: '興味を編集', exact: true }),
+ page.getByRole('button', { name: 'Edit interests', exact: true }),
).toHaveCount(0)
await page.getByRole('link', { name: 'Twitter Lite', exact: true }).click()
await expect(page).toHaveURL(/\/deck$/)
- await page.getByRole('link', { name: 'リーダー', exact: true }).click()
+ await page.getByRole('link', { name: 'Reader', exact: true }).click()
await expect(page).toHaveURL(/\/inbox$/)
await expect(rows).toHaveCount(9)
})
@@ -77,14 +77,14 @@ test('keeps article navigation readable without horizontal overflow', async ({
for (const width of [320, 375, 414, 768]) {
await page.setViewportSize({ width, height: 850 })
await expect(
- page.getByRole('textbox', { name: '記事を検索' }),
+ page.getByRole('textbox', { name: 'Search articles' }),
).toBeVisible()
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
await page.locator('.inbox-article-row').first().click()
const reader = page.getByRole('article', {
- name: 'ブラウザを見ながら考える。AIエージェントとの新しい開発の進め方',
+ name: 'Thinking in the browser: a new workflow with AI agents',
exact: true,
})
await expect(reader).toBeVisible()
@@ -95,7 +95,7 @@ test('keeps article navigation readable without horizontal overflow', async ({
path: testInfo.outputPath(`inbox-reader-${width}.png`),
})
if (width === 320) expect((await a11y().analyze()).violations).toEqual([])
- const back = page.getByRole('button', { name: '記事一覧に戻る' })
+ const back = page.getByRole('button', { name: 'Back to articles' })
if (await back.isVisible()) await back.click()
}
})
diff --git a/e2e/integrations/research.test.ts b/e2e/integrations/research.test.ts
index b4d6f3f..6114bad 100644
--- a/e2e/integrations/research.test.ts
+++ b/e2e/integrations/research.test.ts
@@ -43,9 +43,9 @@ test('restores the conversation and growing temporary deck from reconnect snapsh
}) => {
await page.goto('/deck')
await showDeckManagement(page)
- await page.getByRole('button', { name: 'カラムを追加', exact: true }).click()
+ await page.getByRole('button', { name: 'Add column', exact: true }).click()
const accounts = page.getByRole('combobox', {
- name: '接続プロファイル',
+ name: 'Connection profile',
exact: true,
})
await accounts.selectOption({ label: 'e2e' })
@@ -98,7 +98,7 @@ test('restores the conversation and growing temporary deck from reconnect snapsh
)
await page.reload()
await showResearch(page)
- const conversation = page.getByRole('log', { name: '調査の会話' })
+ const conversation = page.getByRole('log', { name: 'Research conversation' })
await expect(conversation).toContainText('既存の観点を使って調査して')
await expect(conversation).toContainText('list_decks: 完了')
await expect(conversation).toContainText('既存の検索条件を再利用しています。')
@@ -135,11 +135,11 @@ test('restores the conversation and growing temporary deck from reconnect snapsh
])
await closeMobileSidebar(page)
await expect(
- page.getByRole('button', { name: 'デッキとして保存', exact: true }),
+ page.getByRole('button', { name: 'Save as deck', exact: true }),
).toBeEnabled()
await showResearch(page)
const accountPicker = page.getByRole('button', {
- name: '接続アカウント · 1件',
+ name: 'Connected accounts · 1',
})
await accountPicker.click()
await expect(accountPicker).toHaveAttribute('aria-expanded', 'true')
@@ -148,13 +148,11 @@ test('restores the conversation and growing temporary deck from reconnect snapsh
).toBeChecked()
await accountPicker.click()
await expect(accountPicker).toHaveAttribute('aria-expanded', 'false')
- await page.getByLabel('メッセージ', { exact: true }).fill('この観点を詳しく')
+ await page.getByLabel('Message', { exact: true }).fill('この観点を詳しく')
await expect(
- page.getByRole('button', { name: '送信', exact: true }),
+ page.getByRole('button', { name: 'Send', exact: true }),
).toBeEnabled()
- await page
- .getByRole('button', { name: 'サイドバーを閉じる', exact: true })
- .click()
+ await page.getByRole('button', { name: 'Close sidebar', exact: true }).click()
run.deckVersion = 3
if (run.deck) run.deck.title = '閉じている間に更新された調査'
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
@@ -162,7 +160,7 @@ test('restores the conversation and growing temporary deck from reconnect snapsh
{ timeout: 15000 },
)
await showResearch(page)
- await expect(page.getByLabel('メッセージ', { exact: true })).toHaveValue(
+ await expect(page.getByLabel('Message', { exact: true })).toHaveValue(
'この観点を詳しく',
)
await page.setViewportSize({ width: 600, height: 900 })
@@ -170,20 +168,20 @@ test('restores the conversation and growing temporary deck from reconnect snapsh
page.locator('[data-sidebar="sidebar"][data-mobile="true"]'),
).toHaveCount(1)
await showResearch(page)
- await expect(page.getByLabel('メッセージ', { exact: true })).toHaveValue(
+ await expect(page.getByLabel('Message', { exact: true })).toHaveValue(
'この観点を詳しく',
)
- await page.getByRole('button', { name: '接続アカウント · 1件' }).click()
+ await page.getByRole('button', { name: 'Connected accounts · 1' }).click()
await page.getByRole('checkbox', { name: 'e2e · Twitter' }).uncheck()
await page.setViewportSize({ width: 1100, height: 900 })
await expect(
page.locator('[data-sidebar="sidebar"][data-mobile="true"]'),
).toHaveCount(0)
await showResearch(page)
- await expect(page.getByLabel('メッセージ', { exact: true })).toHaveValue(
+ await expect(page.getByLabel('Message', { exact: true })).toHaveValue(
'この観点を詳しく',
)
- await page.getByRole('button', { name: '接続アカウント · 0件' }).click()
+ await page.getByRole('button', { name: 'Connected accounts · 0' }).click()
await expect(
page.getByRole('checkbox', { name: 'e2e · Twitter' }),
).not.toBeChecked()
@@ -192,10 +190,10 @@ test('restores the conversation and growing temporary deck from reconnect snapsh
page.locator('[data-sidebar="sidebar"][data-mobile="true"]'),
).toHaveCount(1)
await showResearch(page)
- await expect(page.getByLabel('メッセージ', { exact: true })).toHaveValue(
+ await expect(page.getByLabel('Message', { exact: true })).toHaveValue(
'この観点を詳しく',
)
- await page.getByRole('button', { name: '接続アカウント · 0件' }).click()
+ await page.getByRole('button', { name: 'Connected accounts · 0' }).click()
await expect(
page.getByRole('checkbox', { name: 'e2e · Twitter' }),
).not.toBeChecked()
@@ -217,9 +215,9 @@ test('renders Markdown and reveals an unloaded citation in an offscreen deck col
await page.emulateMedia({ reducedMotion: 'reduce' })
await page.goto('/deck')
await showDeckManagement(page)
- await page.getByRole('button', { name: 'カラムを追加', exact: true }).click()
+ await page.getByRole('button', { name: 'Add column', exact: true }).click()
const accounts = page.getByRole('combobox', {
- name: '接続プロファイル',
+ name: 'Connection profile',
exact: true,
})
await accounts.selectOption({ label: 'e2e' })
@@ -288,7 +286,7 @@ test('renders Markdown and reveals an unloaded citation in an offscreen deck col
)
await page.reload()
await showResearch(page)
- const chat = page.getByRole('log', { name: '調査の会話' })
+ const chat = page.getByRole('log', { name: 'Research conversation' })
await expect(chat.getByRole('heading', { name: '調査結果' })).toBeVisible()
await expect(chat.getByRole('heading', { name: '調査結果' })).toHaveCSS(
'font-weight',
@@ -309,7 +307,7 @@ test('renders Markdown and reveals an unloaded citation in an offscreen deck col
await expect(card).toBeVisible()
await expect(card).toHaveClass(/citation-highlight/)
await expect(card).toBeFocused()
- await expect(card).toContainText('調査時に取得した投稿')
+ await expect(card).toContainText('Post saved during research')
await expect(card).toContainText('AIが調査時に取得した引用本文')
expect(
await page.locator('.deck-board').evaluate((element) => element.scrollLeft),
@@ -327,23 +325,25 @@ test('explains unconfigured Codex and prevents starting research without affecti
await expect(heading).toBeVisible()
const originalTitle = await heading.textContent()
await showResearch(page)
- const panel = page.getByRole('complementary', { name: '調査チャット' })
+ const panel = page.getByRole('complementary', { name: 'Research chat' })
await expect(panel).toBeVisible()
await expect(
- panel.getByText('自宅のCodexへの接続が設定されていません。'),
+ panel.getByText(
+ 'The connection to Codex on your home server is not configured.',
+ ),
).toBeVisible()
await expect(
- panel.getByRole('button', { name: '送信', exact: true }),
+ panel.getByRole('button', { name: 'Send', exact: true }),
).toBeDisabled()
- await expect(panel.getByLabel('メッセージ', { exact: true })).toBeDisabled()
+ await expect(panel.getByLabel('Message', { exact: true })).toBeDisabled()
const accessibility = await a11y().analyze()
expect(accessibility.violations).toEqual([])
await closeMobileSidebar(page)
await expect(heading).toHaveText(originalTitle ?? '')
await showDeckManagement(page)
- await page.getByRole('button', { name: 'デッキを作成', exact: true }).click()
- await page.getByLabel('新しいデッキ名').fill('手動の調査')
- await page.getByRole('button', { name: '作成', exact: true }).click()
+ await page.getByRole('button', { name: 'Create deck', exact: true }).click()
+ await page.getByLabel('New deck name').fill('手動の調査')
+ await page.getByRole('button', { name: 'Create', exact: true }).click()
await closeMobileSidebar(page)
await expect(heading).toHaveText('手動の調査')
await showResearch(page)
diff --git a/e2e/integrations/support.test.ts b/e2e/integrations/support.test.ts
index cc65dd2..d49cfed 100644
--- a/e2e/integrations/support.test.ts
+++ b/e2e/integrations/support.test.ts
@@ -1,7 +1,7 @@
import { expect, test } from '../fixtures'
-const reviewSubject = '来週のレビュー、日程を決める'
-const coffeeSubject = '久しぶりに、お茶でも'
+const reviewSubject = 'Schedule next week’s design review'
+const coffeeSubject = 'Catch up over coffee'
test('reviews an edited reply and keeps the simulated result in history', async ({
page,
@@ -12,28 +12,31 @@ test('reviews an edited reply and keeps the simulated result in history', async
const rows = page.locator('.support-row')
await expect(rows).toHaveCount(3)
await rows.filter({ hasText: reviewSubject }).click()
- await page.getByRole('button', { name: '丁寧に', exact: true }).click()
- const reply = page.getByRole('textbox', { name: '返信', exact: true })
- await expect(reply).toHaveValue(/資料をご共有/)
- const edited = '資料を確認しました。初回の導線について当日お話しします。'
+ await page.getByRole('button', { name: 'Polite', exact: true }).click()
+ const reply = page.getByRole('textbox', { name: 'Reply', exact: true })
+ await expect(reply).toHaveValue(/Thank you for sharing/)
+ const edited =
+ 'I reviewed the materials. Let’s discuss the first-time experience.'
await reply.fill(edited)
- const slot = '10月5日(月)14:00–14:30'
- await page.getByRole('combobox', { name: '日程候補' }).selectOption(slot)
- await page.getByRole('button', { name: '返信に入れる' }).click()
- await expect(reply).toHaveValue(`${edited}\n\n${slot}はいかがでしょうか。`)
- await page.getByRole('button', { name: '送信内容を確認' }).click()
- const dialog = page.getByRole('dialog', { name: '送信内容の確認' })
+ const slot = 'Mon, Oct 5, 14:00–14:30'
+ await page
+ .getByRole('combobox', { name: 'Suggested times' })
+ .selectOption(slot)
+ await page.getByRole('button', { name: 'Add to reply' }).click()
+ await expect(reply).toHaveValue(`${edited}\n\nWould ${slot} work for you?`)
+ await page.getByRole('button', { name: 'Review reply' }).click()
+ const dialog = page.getByRole('dialog', { name: 'Review reply' })
await expect(dialog).toContainText(edited)
await expect(dialog).toContainText(slot)
- await expect(dialog).toContainText('外部には送信されません')
+ await expect(dialog).toContainText('No message will be sent')
expect((await a11y().analyze()).violations).toEqual([])
- await dialog.getByRole('button', { name: '戻る', exact: true }).click()
+ await dialog.getByRole('button', { name: 'Back', exact: true }).click()
await expect(reply).toHaveValue(new RegExp(edited))
- await page.getByRole('button', { name: '送信内容を確認' }).click()
- await dialog.getByRole('button', { name: '送信を試す' }).click()
+ await page.getByRole('button', { name: 'Review reply' }).click()
+ await dialog.getByRole('button', { name: 'Try sending' }).click()
await expect(dialog).toHaveCount(0)
await expect(rows.filter({ hasText: reviewSubject })).toHaveCount(0)
- await page.getByRole('button', { name: '履歴', exact: true }).click()
+ await page.getByRole('button', { name: 'History', exact: true }).click()
await rows.filter({ hasText: reviewSubject }).click()
await expect(page.locator('.support-outcome')).toContainText(edited)
await expect(page.locator('.support-outcome')).toContainText(slot)
@@ -47,21 +50,21 @@ test('defers a conversation and restores it to the current queue', async ({
const rows = page.locator('.support-row')
await rows.filter({ hasText: coffeeSubject }).click()
await page
- .getByRole('textbox', { name: '返信', exact: true })
+ .getByRole('textbox', { name: 'Reply', exact: true })
.fill('土曜の予定を確認してから返事するね。')
- await page.getByRole('button', { name: '今は置いておく' }).click()
+ await page.getByRole('button', { name: 'Leave for later' }).click()
await expect(rows.filter({ hasText: coffeeSubject })).toHaveCount(0)
- await page.getByRole('button', { name: 'あとで', exact: true }).click()
+ await page.getByRole('button', { name: 'Later', exact: true }).click()
await rows.filter({ hasText: coffeeSubject }).click()
await expect(
- page.getByRole('textbox', { name: '返信', exact: true }),
+ page.getByRole('textbox', { name: 'Reply', exact: true }),
).toHaveValue('土曜の予定を確認してから返事するね。')
- await page.getByRole('button', { name: '今に戻す' }).click()
- const back = page.getByRole('button', { name: '連絡一覧に戻る' })
+ await page.getByRole('button', { name: 'Move to Now' }).click()
+ const back = page.getByRole('button', { name: 'Back to messages' })
if (await back.isVisible()) await back.click()
await expect(rows.filter({ hasText: coffeeSubject })).toBeVisible()
await expect(
- page.getByRole('button', { name: '今', exact: true }),
+ page.getByRole('button', { name: 'Now', exact: true }),
).toHaveAttribute('aria-pressed', 'true')
})
@@ -72,44 +75,52 @@ test('keeps corrections as new records and summarizes only selected current fact
await page.goto('/journal')
await page.waitForLoadState('networkidle')
await expect(
- page.getByRole('button', { name: '記録する', exact: true }),
+ page.getByRole('button', { name: 'Save entry', exact: true }),
).toBeDisabled()
await page
- .getByRole('textbox', { name: '記録内容' })
+ .getByRole('textbox', { name: 'Entry text' })
.fill('次はレビュー資料の導線を確認する')
- await page.getByRole('button', { name: '記録する', exact: true }).click()
- await expect(page.getByRole('article', { name: 'メモの記録' })).toContainText(
- '発生日時: 不明',
+ await page.getByRole('button', { name: 'Save entry', exact: true }).click()
+ await expect(page.getByRole('article', { name: 'Note entry' })).toContainText(
+ 'Occurred: Unknown',
)
- await page.getByRole('button', { name: '体重', exact: true }).click()
- await page.getByRole('spinbutton', { name: '体重', exact: true }).fill('65.2')
- await page.getByRole('textbox', { name: '記録元' }).fill('自宅の体重計')
- await page.getByRole('button', { name: '記録する', exact: true }).click()
+ await page.getByRole('button', { name: 'Weight', exact: true }).click()
await page
- .getByRole('article', { name: '体重の記録' })
- .getByRole('button', { name: '訂正する' })
+ .getByRole('spinbutton', { name: 'Weight', exact: true })
+ .fill('65.2')
+ await page.getByRole('textbox', { name: 'Source' }).fill('自宅の体重計')
+ await page.getByRole('button', { name: 'Save entry', exact: true }).click()
+ await page
+ .getByRole('article', { name: 'Weight entry' })
+ .getByRole('button', { name: 'Correct' })
.click()
- const correction = page.getByRole('dialog', { name: '記録を訂正' })
+ const correction = page.getByRole('dialog', { name: 'Correct entry' })
await correction
- .getByRole('spinbutton', { name: '体重', exact: true })
+ .getByRole('spinbutton', { name: 'Weight', exact: true })
.fill('65.8')
- await correction.getByRole('button', { name: '訂正を追加' }).click()
- const weights = page.getByRole('article', { name: '体重の記録' })
+ await correction.getByRole('button', { name: 'Add correction' }).click()
+ const weights = page.getByRole('article', { name: 'Weight entry' })
await expect(weights).toHaveCount(2)
- await expect(weights.filter({ hasText: '65.2 kg' })).toContainText('訂正前')
- await expect(weights.filter({ hasText: '65.8 kg' })).toContainText('訂正')
+ await expect(weights.filter({ hasText: '65.2 kg' })).toContainText('Original')
+ await expect(weights.filter({ hasText: '65.8 kg' })).toContainText(
+ 'Correction',
+ )
await page
- .getByRole('button', { name: '記録をまとめる', exact: true })
+ .getByRole('button', { name: 'Summarize entries', exact: true })
.click()
- const review = page.getByRole('dialog', { name: '記録をまとめる' })
- const summary = review.getByRole('textbox', { name: '記録のまとめ' })
+ const review = page.getByRole('dialog', { name: 'Summarize entries' })
+ const summary = review.getByRole('textbox', { name: 'Entry summary' })
await expect(summary).toHaveValue(/65.8 kg/)
await expect(summary).not.toHaveValue(/65.2 kg/)
- await expect(summary).toHaveValue(/記録元: 自宅の体重計/)
- await expect(summary).toHaveValue(/発生日時が不明の記録: 2件/)
+ await expect(summary).toHaveValue(/Source: 自宅の体重計/)
+ await expect(summary).toHaveValue(
+ /Entries with an unknown event time: 2 entries/,
+ )
await review.getByRole('checkbox', { name: /次はレビュー資料/ }).uncheck()
await expect(summary).not.toHaveValue(/次はレビュー資料/)
- await expect(summary).toHaveValue(/記録のない期間の服薬・体重は不明/)
+ await expect(summary).toHaveValue(
+ /Medication and weight are unknown for periods without entries/,
+ )
expect((await a11y().analyze()).violations).toEqual([])
})
@@ -126,18 +137,18 @@ test('fits communication and journal interactions in narrow screens', async ({
).toBe(width)
await page.locator('.support-row').first().click()
await expect(
- page.getByRole('textbox', { name: '返信', exact: true }),
+ page.getByRole('textbox', { name: 'Reply', exact: true }),
).toBeVisible()
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
if (width === 320) expect((await a11y().analyze()).violations).toEqual([])
- const back = page.getByRole('button', { name: '連絡一覧に戻る' })
+ const back = page.getByRole('button', { name: 'Back to messages' })
if (await back.isVisible()) await back.click()
- await page.getByRole('link', { name: '記録', exact: true }).click()
- await page.getByRole('button', { name: '体重', exact: true }).click()
+ await page.getByRole('link', { name: 'Journal', exact: true }).click()
+ await page.getByRole('button', { name: 'Weight', exact: true }).click()
await expect(
- page.getByRole('spinbutton', { name: '体重', exact: true }),
+ page.getByRole('spinbutton', { name: 'Weight', exact: true }),
).toBeVisible()
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
diff --git a/e2e/integrations/vitals.test.ts b/e2e/integrations/vitals.test.ts
index af5a622..fffaa31 100644
--- a/e2e/integrations/vitals.test.ts
+++ b/e2e/integrations/vitals.test.ts
@@ -17,7 +17,7 @@ test('filters body systems and opens lab history', async ({ page, a11y }) => {
const dialog = page.getByRole('dialog', { name: 'Cardiovascular' })
await expect(dialog).toContainText('68')
await dialog
- .getByRole('button', { name: '閉じる', exact: true })
+ .getByRole('button', { name: 'Close', exact: true })
.last()
.click()
await page.getByRole('button', { name: 'Lab history', exact: true }).click()
@@ -38,7 +38,7 @@ test('explores environment charts and simulated climate controls', async ({
}) => {
await page.goto('/vitals')
await page.waitForLoadState('networkidle')
- const menu = page.getByRole('button', { name: 'Healthメニューを開く' })
+ const menu = page.getByRole('button', { name: 'Open Health menu' })
if (await menu.isVisible()) await menu.click()
await page
.getByRole('complementary', { name: 'Health navigation' })
@@ -95,7 +95,7 @@ test('keeps both dashboards within the viewport', async ({
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
await page.screenshot({ path: testInfo.outputPath(`body-${width}.png`) })
- const menu = page.getByRole('button', { name: 'Healthメニューを開く' })
+ const menu = page.getByRole('button', { name: 'Open Health menu' })
if (await menu.isVisible()) await menu.click()
const navigation = page.getByRole('complementary', {
name: 'Health navigation',
diff --git a/e2e/integrations/webmcp.test.ts b/e2e/integrations/webmcp.test.ts
index 5ea17e7..b1be344 100644
--- a/e2e/integrations/webmcp.test.ts
+++ b/e2e/integrations/webmcp.test.ts
@@ -117,9 +117,9 @@ test('creates temporary research, edits it, persists explicitly and reopens it o
).toBeVisible()
await page
.getByRole('region', { name: '日本語', exact: true })
- .getByRole('button', { name: '日本語の操作' })
+ .getByRole('button', { name: 'Actions for 日本語' })
.click()
- await page.getByRole('menuitem', { name: '編集', exact: true }).click()
+ await page.getByRole('menuitem', { name: 'Edit', exact: true }).click()
const replaced = await executeTool(page, 'set_deck', {
deckId: deck.id,
title: '更新した調査',
@@ -234,7 +234,9 @@ test('keeps temporary views independent and loses only unsaved views on reload',
).toBe(false)
expect(savedCount()).toEqual({ count: 0 })
await page.reload()
- await expect(page.getByRole('heading', { level: 1 })).toHaveText('新しい調査')
+ await expect(page.getByRole('heading', { level: 1 })).toHaveText(
+ 'New research',
+ )
})
test('rejects invalid input without changing the active view', async ({
diff --git a/e2e/integrations/workspace.test.ts b/e2e/integrations/workspace.test.ts
index 168f898..11bc7b8 100644
--- a/e2e/integrations/workspace.test.ts
+++ b/e2e/integrations/workspace.test.ts
@@ -9,10 +9,10 @@ test('keeps the sidebar, composer and deck reachable at compact widths', async (
for (const width of [320, 375, 414, 768]) {
await page.setViewportSize({ width, height: 850 })
await showResearch(page)
- const chat = page.getByRole('complementary', { name: '調査チャット' })
+ const chat = page.getByRole('complementary', { name: 'Research chat' })
await expect(chat).toBeVisible()
const composer = chat.getByRole('textbox', {
- name: 'メッセージ',
+ name: 'Message',
exact: true,
})
await expect(composer).toBeVisible()
@@ -30,12 +30,12 @@ test('keeps the sidebar, composer and deck reachable at compact widths', async (
if (width === 320) expect((await a11y().analyze()).violations).toEqual([])
await closeMobileSidebar(page)
}
- await page.getByRole('button', { name: 'サイドバーを切り替え' }).click()
+ await page.getByRole('button', { name: 'Toggle sidebar' }).click()
await expect(
- page.getByRole('complementary', { name: '調査チャット' }),
+ page.getByRole('complementary', { name: 'Research chat' }),
).toBeHidden()
- await page.getByRole('button', { name: '調査チャット', exact: true }).click()
+ await page.getByRole('button', { name: 'Research chat', exact: true }).click()
await expect(
- page.getByRole('complementary', { name: '調査チャット' }),
+ page.getByRole('complementary', { name: 'Research chat' }),
).toBeVisible()
})
diff --git a/e2e/workspace.ts b/e2e/workspace.ts
index 5cbbf67..1a4b516 100644
--- a/e2e/workspace.ts
+++ b/e2e/workspace.ts
@@ -2,28 +2,28 @@ import { expect, type Page } from '@playwright/test'
export async function showDeckManagement(page: Page) {
await expect(
- page.getByRole('button', { name: '名前を変更', includeHidden: true }),
+ page.getByRole('button', { name: 'Rename', includeHidden: true }),
).toBeEnabled()
const navigation = page.getByRole('button', {
- name: 'デッキ管理',
+ name: 'Deck manager',
exact: true,
})
if (!(await navigation.isVisible())) {
- await page.getByRole('button', { name: 'サイドバーを切り替え' }).click()
+ await page.getByRole('button', { name: 'Toggle sidebar' }).click()
}
await navigation.click()
}
export async function showResearch(page: Page) {
await expect(
- page.getByRole('button', { name: '名前を変更', includeHidden: true }),
+ page.getByRole('button', { name: 'Rename', includeHidden: true }),
).toBeEnabled()
const navigation = page.getByRole('button', {
- name: '調査チャット',
+ name: 'Research chat',
exact: true,
})
if (!(await navigation.isVisible())) {
- await page.getByRole('button', { name: 'サイドバーを切り替え' }).click()
+ await page.getByRole('button', { name: 'Toggle sidebar' }).click()
}
await navigation.click()
}
@@ -32,7 +32,7 @@ export async function closeMobileSidebar(page: Page) {
const sheet = page.locator('[data-mobile="true"][data-sidebar="sidebar"]')
if (await sheet.isVisible()) {
await page
- .getByRole('button', { name: 'サイドバーを閉じる', exact: true })
+ .getByRole('button', { name: 'Close sidebar', exact: true })
.click()
await expect(sheet).toBeHidden()
}
diff --git a/src/components/app-shell.tsx b/src/components/app-shell.tsx
index d15170a..2a9b5ab 100644
--- a/src/components/app-shell.tsx
+++ b/src/components/app-shell.tsx
@@ -46,8 +46,8 @@ function Workspace({
const [panel, setPanel] = useState<'chat' | 'decks'>('chat')
const { setOpen, setOpenMobile, isMobile } = useSidebar()
const navigation = [
- { id: 'chat', title: '調査チャット', icon: MessageSquare },
- { id: 'decks', title: 'デッキ管理', icon: Columns3 },
+ { id: 'chat', title: 'Research chat', icon: MessageSquare },
+ { id: 'decks', title: 'Deck manager', icon: Columns3 },
] as const
return (
@@ -61,8 +61,8 @@ function Workspace({
footer={
{deck.persisted - ? 'サーバーに保存・端末間で共有' - : '一時ビュー · このタブのみ'} + ? 'Saved to server · Shared across devices' + : 'Temporary view · This tab only'}
> } @@ -210,7 +210,7 @@ export function DeckPage() {- 保存しています… + Saving…
) : null} {legacyAvailable ? (- このブラウザに旧デッキがあります。 + Legacy decks are available in this browser.
) : null} {!ready ? (- デッキを読み込んでいます… + Loading decks…
) : null} {profiles.isError ? (- 接続プロファイルを取得できませんでした。 + Unable to load connection profiles.
) : null} {oauthResult === 'failed' ? (- Mastodonの認可を完了できませんでした。接続アカウントからやり直してください。 + Could not complete Mastodon authorization. Try again from + Connected accounts.
) : null} {oauthResult === 'connected' ? (- Mastodonアカウントを接続しました。カラムの接続先に選べます。 + Mastodon account connected. You can now use it in a column.
) : null} {profiles.data?.relayError ? ( @@ -300,13 +301,13 @@ export function DeckPage() { ) : null} {profiles.isPending ? ( -接続プロファイルを取得しています…
+Loading connection profiles…
) : null} {!renaming && editing === null && !switcherEditing ? storageErrorNotice : null} {renaming ? ( -このデッキとカラムの設定を削除します。
+Delete this deck and its column settings.
- 全文検索の対象はサーバーの設定によって異なります。結果が0件でも、検索に対応しているとは限りません。 + Full-text search depends on server settings. Zero results do not + necessarily mean search is supported.