feat: translate system interface to English
This commit is contained in:
@@ -24,11 +24,11 @@ export function DailyBrief({
|
||||
)
|
||||
|
||||
return (
|
||||
<section className="home-brief" aria-label="今日のブリーフ">
|
||||
<section className="home-brief" aria-label="Daily brief">
|
||||
<p>
|
||||
{focus
|
||||
? `今日は「${focus.title}」から始められそうです。`
|
||||
: 'いま優先して取りかかるタスクはありません。'}
|
||||
? `Start with “${focus.title}” today.`
|
||||
: 'There are no priority tasks right now.'}
|
||||
{firstContact ? (
|
||||
<>
|
||||
{' '}
|
||||
@@ -36,27 +36,27 @@ export function DailyBrief({
|
||||
type="button"
|
||||
onClick={() => onOpenContact(firstContact.id)}
|
||||
>
|
||||
{firstContact.name}さんへの返信
|
||||
Reply to {firstContact.name}
|
||||
</button>
|
||||
{contacts.length > 1
|
||||
? `など、確認できる連絡が${contacts.length}件あります。`
|
||||
: 'が残っています。'}
|
||||
? ` — one of ${contacts.length} conversations to review.`
|
||||
: ' when you’re ready.'}
|
||||
</>
|
||||
) : (
|
||||
' いま返信に取りかかる連絡もありません。'
|
||||
' There are no conversations awaiting your attention.'
|
||||
)}
|
||||
</p>
|
||||
{resume ? (
|
||||
<p className="home-brief-secondary">
|
||||
再開メモには「
|
||||
Your resume note: “
|
||||
{resume.text.length > 100
|
||||
? `${resume.text.slice(0, 100)}…`
|
||||
: resume.text}
|
||||
」と残っています。
|
||||
”
|
||||
</p>
|
||||
) : optional ? (
|
||||
<p className="home-brief-secondary">
|
||||
「{optional.title}」は、余裕があるときに。
|
||||
“{optional.title}” can wait until you have room.
|
||||
</p>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
@@ -23,7 +23,10 @@ import { WorkspaceNavigation } from '#/components/workspace-navigation'
|
||||
import { articles } from '#/features/inbox/mock-articles'
|
||||
import { ConversationDetail } from '#/features/support/conversation-detail'
|
||||
import { conversations } from '#/features/support/mock-conversations'
|
||||
import { useWorkspaceState } from '#/features/workspace/workspace-state'
|
||||
import {
|
||||
entryKindLabels,
|
||||
useWorkspaceState,
|
||||
} from '#/features/workspace/workspace-state'
|
||||
import { DailyBrief } from './daily-brief'
|
||||
import { type HomeAction, interpretAction } from './interpret-action'
|
||||
import './home.css'
|
||||
@@ -138,7 +141,7 @@ export function HomePage() {
|
||||
void navigate({ to: action.to })
|
||||
break
|
||||
}
|
||||
setNotice(proposal?.label ?? '反映しました')
|
||||
setNotice(proposal?.label ?? 'Updated')
|
||||
setProposal(null)
|
||||
setPrompt('')
|
||||
}
|
||||
@@ -171,8 +174,8 @@ export function HomePage() {
|
||||
<div className="home-scroll">
|
||||
<div className="home-content">
|
||||
<header className="home-greeting">
|
||||
<p className="text-sm text-muted-foreground">今日</p>
|
||||
<h1>おはようございます、Yutaさん</h1>
|
||||
<p className="text-sm text-muted-foreground">Today</p>
|
||||
<h1>Good morning, Yuta</h1>
|
||||
<DailyBrief onOpenContact={setContactId} />
|
||||
</header>
|
||||
<form
|
||||
@@ -184,8 +187,8 @@ export function HomePage() {
|
||||
>
|
||||
<Textarea
|
||||
ref={input}
|
||||
aria-label="ホームへの入力"
|
||||
placeholder="頭にあることを、そのまま。"
|
||||
aria-label="Home input"
|
||||
placeholder="What’s on your mind?"
|
||||
value={prompt}
|
||||
onChange={(event) => {
|
||||
setPrompt(event.target.value)
|
||||
@@ -205,12 +208,12 @@ export function HomePage() {
|
||||
/>
|
||||
<div className="home-composer-footer">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
やること、返信、メモ。
|
||||
Tasks, replies, notes.
|
||||
</span>
|
||||
<Button
|
||||
type="submit"
|
||||
size="icon"
|
||||
aria-label="候補を表示"
|
||||
aria-label="Preview action"
|
||||
disabled={!prompt.trim()}
|
||||
>
|
||||
<ArrowUp />
|
||||
@@ -219,9 +222,9 @@ export function HomePage() {
|
||||
</form>
|
||||
<div className="home-suggestions">
|
||||
{[
|
||||
'買い物を追加',
|
||||
'青木さんに返信',
|
||||
'メモ:次は資料の2ページ目から',
|
||||
'Add grocery shopping',
|
||||
'Reply to Aoki',
|
||||
'Note: Resume on page 2',
|
||||
].map((text) => (
|
||||
<button
|
||||
type="button"
|
||||
@@ -236,13 +239,11 @@ export function HomePage() {
|
||||
{proposal && (
|
||||
<div className="home-proposal">
|
||||
<p>{proposal.label}</p>
|
||||
<Button onClick={() => apply(proposal.action)}>
|
||||
実行する
|
||||
</Button>
|
||||
<Button onClick={() => apply(proposal.action)}>Apply</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label="候補を取り消す"
|
||||
aria-label="Dismiss suggestion"
|
||||
onClick={() => setProposal(null)}
|
||||
>
|
||||
<X />
|
||||
@@ -253,12 +254,12 @@ export function HomePage() {
|
||||
{notice}
|
||||
</p>
|
||||
<div className="home-columns">
|
||||
<section className="home-plan" aria-label="今日の予定">
|
||||
<section className="home-plan" aria-label="Today’s plan">
|
||||
{(
|
||||
[
|
||||
{ id: 'focus', label: 'まずこれ' },
|
||||
{ id: 'routine', label: 'いつものこと' },
|
||||
{ id: 'optional', label: '余裕があれば' },
|
||||
{ id: 'focus', label: 'Start here' },
|
||||
{ id: 'routine', label: 'Routines' },
|
||||
{ id: 'optional', label: 'If there’s room' },
|
||||
] as const
|
||||
).map((section) => (
|
||||
<section key={section.id} className="home-section">
|
||||
@@ -267,14 +268,14 @@ export function HomePage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={`${section.label}に追加`}
|
||||
aria-label={`Add to ${section.label}`}
|
||||
onClick={() =>
|
||||
suggest(
|
||||
section.id === 'routine'
|
||||
? 'ルーティンに追加:'
|
||||
? 'Add routine:'
|
||||
: section.id === 'optional'
|
||||
? '余裕があれば '
|
||||
: '追加:',
|
||||
? 'Optional: '
|
||||
: 'Add:',
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -292,7 +293,7 @@ export function HomePage() {
|
||||
<button
|
||||
type="button"
|
||||
className="home-check"
|
||||
aria-label={`${task.title}を完了`}
|
||||
aria-label={`Complete ${task.title}`}
|
||||
onClick={() =>
|
||||
setTasks((current) =>
|
||||
current.map((item) =>
|
||||
@@ -309,7 +310,7 @@ export function HomePage() {
|
||||
<p>{task.title}</p>
|
||||
{task.steps && (
|
||||
<details>
|
||||
<summary>小さく進める</summary>
|
||||
<summary>One step at a time</summary>
|
||||
<div className="home-steps">
|
||||
{task.steps.map((step) => (
|
||||
<label
|
||||
@@ -360,7 +361,7 @@ export function HomePage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={`${task.title}を上へ`}
|
||||
aria-label={`Move ${task.title} up`}
|
||||
onClick={() => moveTask(task.id, -1)}
|
||||
>
|
||||
<ArrowUp />
|
||||
@@ -368,7 +369,7 @@ export function HomePage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={`${task.title}を下へ`}
|
||||
aria-label={`Move ${task.title} down`}
|
||||
onClick={() => moveTask(task.id, 1)}
|
||||
>
|
||||
<ArrowDown />
|
||||
@@ -376,7 +377,7 @@ export function HomePage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label={`${task.title}をあとで`}
|
||||
aria-label={`Defer ${task.title}`}
|
||||
onClick={() =>
|
||||
setTasks((current) =>
|
||||
current.map((item) =>
|
||||
@@ -387,7 +388,7 @@ export function HomePage() {
|
||||
)
|
||||
}
|
||||
>
|
||||
あとで
|
||||
Later
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -402,7 +403,7 @@ export function HomePage() {
|
||||
onClick={() => setContactId(item.id)}
|
||||
>
|
||||
<span className="block">
|
||||
{item.name}さんに返信
|
||||
Reply to {item.name}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{item.subject}
|
||||
@@ -411,7 +412,7 @@ export function HomePage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={`${item.name}との会話を開く`}
|
||||
aria-label={`Open conversation with ${item.name}`}
|
||||
onClick={() => setContactId(item.id)}
|
||||
>
|
||||
<ChevronRight />
|
||||
@@ -424,7 +425,7 @@ export function HomePage() {
|
||||
) &&
|
||||
(section.id !== 'focus' || !waiting.length) && (
|
||||
<p className="home-empty">
|
||||
ここは空けておいても大丈夫。
|
||||
It’s okay to leave some room.
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
@@ -432,7 +433,7 @@ export function HomePage() {
|
||||
{rest.length > 0 && (
|
||||
<details className="home-rest">
|
||||
<summary>
|
||||
あとで・終えたこと <span>{rest.length}</span>
|
||||
Later & done <span>{rest.length}</span>
|
||||
</summary>
|
||||
{rest.map((task) => (
|
||||
<div className="home-task" key={task.id}>
|
||||
@@ -445,7 +446,7 @@ export function HomePage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={`${task.title}を戻す`}
|
||||
aria-label={`Restore ${task.title}`}
|
||||
onClick={() =>
|
||||
setTasks((current) =>
|
||||
current.map((item) =>
|
||||
@@ -463,11 +464,11 @@ export function HomePage() {
|
||||
</details>
|
||||
)}
|
||||
</section>
|
||||
<aside className="home-context" aria-label="今日のつながり">
|
||||
<aside className="home-context" aria-label="Today’s context">
|
||||
<section className="home-section">
|
||||
<header className="home-section-heading">
|
||||
<h2>連絡</h2>
|
||||
<Link to="/support" aria-label="連絡をすべて見る">
|
||||
<h2>Messages</h2>
|
||||
<Link to="/support" aria-label="View all messages">
|
||||
<ArrowUpRight className="size-4" />
|
||||
</Link>
|
||||
</header>
|
||||
@@ -488,9 +489,9 @@ export function HomePage() {
|
||||
<span>{item.name}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{statuses[item.id] === 'waiting'
|
||||
? '相手待ち'
|
||||
? 'Waiting'
|
||||
: statuses[item.id] === 'later'
|
||||
? 'あとで'
|
||||
? 'Later'
|
||||
: item.channels.join(' / ')}
|
||||
</span>
|
||||
</button>
|
||||
@@ -498,15 +499,15 @@ export function HomePage() {
|
||||
</section>
|
||||
<section className="home-section">
|
||||
<header className="home-section-heading">
|
||||
<h2>記録</h2>
|
||||
<Link to="/journal" aria-label="記録を開く">
|
||||
<h2>Journal</h2>
|
||||
<Link to="/journal" aria-label="Open journal">
|
||||
<ArrowUpRight className="size-4" />
|
||||
</Link>
|
||||
</header>
|
||||
{currentEntries.slice(0, 2).map((entry) => (
|
||||
<p className="home-record" key={entry.id}>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{entry.kind}
|
||||
{entryKindLabels[entry.kind]}
|
||||
</span>
|
||||
<span>
|
||||
{entry.text}
|
||||
@@ -518,17 +519,17 @@ export function HomePage() {
|
||||
<button
|
||||
type="button"
|
||||
className="home-context-row"
|
||||
onClick={() => suggest('メモ:')}
|
||||
onClick={() => suggest('Note:')}
|
||||
>
|
||||
<span>ひとこと残す</span>
|
||||
<span>Leave a note</span>
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
<section className="home-section">
|
||||
<header className="home-section-heading">
|
||||
<h2>読みもの</h2>
|
||||
<Link to="/inbox" aria-label="リーダーを開く">
|
||||
<h2>Reading</h2>
|
||||
<Link to="/inbox" aria-label="Open reader">
|
||||
<ArrowUpRight className="size-4" />
|
||||
</Link>
|
||||
</header>
|
||||
@@ -547,7 +548,7 @@ export function HomePage() {
|
||||
))}
|
||||
</section>
|
||||
<Link to="/deck" className="home-deck-link">
|
||||
Twitter Liteで調べる
|
||||
Explore in Twitter Lite
|
||||
<ArrowUpRight className="size-4" />
|
||||
</Link>
|
||||
</aside>
|
||||
@@ -557,7 +558,7 @@ export function HomePage() {
|
||||
</SidebarInset>
|
||||
{contact && (
|
||||
<Dialog
|
||||
title={`${contact.name}との連絡`}
|
||||
title={`Conversation with ${contact.name}`}
|
||||
className="sm:max-w-3xl"
|
||||
onClose={() => setContactId(undefined)}
|
||||
>
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('home natural-language proposals', () => {
|
||||
['メモ:散歩でアイデアを思いついた', 'メモ', '散歩でアイデアを思いついた'],
|
||||
['再開地点:レビューの続き', '再開地点', 'レビューの続き'],
|
||||
['体重は65.2kg', '体重', '65.2'],
|
||||
['薬を飲んだ', '服薬', '服薬した'],
|
||||
['薬を飲んだ', '服薬', 'Took medication'],
|
||||
])('records explicit user statements: %s', (input, kind, text) => {
|
||||
expect(interpretAction(input, tasks, contacts)).toMatchObject({
|
||||
action: { type: 'journal', kind, text },
|
||||
@@ -155,3 +155,56 @@ describe('home natural-language proposals', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('English home commands', () => {
|
||||
const tasks = [{ id: 'shop', title: 'grocery shopping' }]
|
||||
const contacts = [{ id: 'aoki', name: 'Haruka Aoki' }]
|
||||
it.each([
|
||||
[
|
||||
'Add grocery shopping',
|
||||
{ type: 'add-task', title: 'grocery shopping', section: 'focus' },
|
||||
],
|
||||
[
|
||||
'Add routine: Stretch',
|
||||
{ type: 'add-task', title: 'Stretch', section: 'routine' },
|
||||
],
|
||||
[
|
||||
'Optional: Read',
|
||||
{ type: 'add-task', title: 'Read', section: 'optional' },
|
||||
],
|
||||
['Reply to Aoki', { type: 'contact', id: 'aoki' }],
|
||||
[
|
||||
'Note: Resume on page 2',
|
||||
{ type: 'journal', kind: 'メモ', text: 'Resume on page 2' },
|
||||
],
|
||||
['Resume: Page 2', { type: 'journal', kind: '再開地点', text: 'Page 2' }],
|
||||
['Weight: 65.2 kg', { type: 'journal', kind: '体重', text: '65.2' }],
|
||||
[
|
||||
'Complete grocery shopping',
|
||||
{ type: 'task-status', id: 'shop', status: 'done' },
|
||||
],
|
||||
[
|
||||
'Defer grocery shopping',
|
||||
{ type: 'task-status', id: 'shop', status: 'later' },
|
||||
],
|
||||
[
|
||||
'Restore grocery shopping',
|
||||
{ type: 'task-status', id: 'shop', status: 'todo' },
|
||||
],
|
||||
[
|
||||
'Move grocery shopping up',
|
||||
{ type: 'task-move', id: 'shop', direction: -1 },
|
||||
],
|
||||
['Open reader', { type: 'navigate', to: '/inbox' }],
|
||||
])('interprets %s', (input, action) => {
|
||||
expect(interpretAction(input as string, tasks, contacts)).toMatchObject({
|
||||
action,
|
||||
})
|
||||
})
|
||||
it.each([
|
||||
"Don't complete grocery shopping",
|
||||
'Send a reply to Aoki',
|
||||
])('rejects %s', (input) => {
|
||||
expect(interpretAction(input, tasks, contacts)).toHaveProperty('error')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ type Interpretation = { action: HomeAction; label: string } | { error: string }
|
||||
|
||||
const unsupported = {
|
||||
error:
|
||||
'まだ読み取れません。「買い物を追加」「青木さんに返信」「メモ:気づいたこと」のように、ひとつずつ入力してください。',
|
||||
'Try one action at a time, such as “Add grocery shopping”, “Reply to Aoki”, or “Note: A thought”.',
|
||||
}
|
||||
|
||||
/** Local, deterministic prototype. Returns a proposal; never performs an action. */
|
||||
@@ -29,18 +29,50 @@ export function interpretAction(
|
||||
tasks: Array<{ id: string; title: string; steps?: string[] }>,
|
||||
contacts: Array<{ id: string; name: string }>,
|
||||
): Interpretation {
|
||||
const text = input
|
||||
let text = input
|
||||
.trim()
|
||||
.replace(/[。!!]$/, '')
|
||||
.trim()
|
||||
if (!text) return { error: 'やりたいことを入力してください。' }
|
||||
// Normalize English commands into the same small, deterministic grammar.
|
||||
if (/\b(?:don't|do not|cancel|send)\b/i.test(text))
|
||||
return { error: 'Please review this action in the relevant screen.' }
|
||||
text = text
|
||||
.replace(
|
||||
/^open (messages|journal|reader|twitter lite|deck)$/i,
|
||||
(_, name: string) =>
|
||||
({
|
||||
messages: '連絡',
|
||||
journal: '記録',
|
||||
reader: 'リーダー',
|
||||
'twitter lite': 'Twitter Lite',
|
||||
deck: 'デッキ',
|
||||
})[name.toLowerCase()] ?? name,
|
||||
)
|
||||
.replace(/^note\s*:/i, 'メモ:')
|
||||
.replace(/^resume(?: point)?\s*:/i, '再開地点:')
|
||||
.replace(/^weight\s*:?\s*/i, '体重:')
|
||||
.replace(/^log medication$|^took (?:my )?medication$/i, '服薬した')
|
||||
.replace(/^reply to (.+)$/i, '$1に返信')
|
||||
.replace(/^defer (.+)$/i, '$1を保留')
|
||||
.replace(/^mark (.+) (?:as )?waiting$/i, '$1を相手待ち')
|
||||
.replace(
|
||||
/^move (.+) (up|down)$/i,
|
||||
(_, title: string, direction: string) =>
|
||||
`${title}を${direction.toLowerCase() === 'up' ? '上' : '下'}に移動`,
|
||||
)
|
||||
.replace(/^complete (.+)$/i, '$1を完了')
|
||||
.replace(/^restore (.+)$/i, '$1を未完了')
|
||||
.replace(/^add routine\s*:\s*(.+)$/i, '$1をルーティンに追加')
|
||||
.replace(/^optional\s*:\s*(.+)$/i, '余裕があれば $1')
|
||||
.replace(/^add\s*:?\s+(.+)$/i, '$1を追加')
|
||||
if (!text) return { error: 'Enter what you would like to do.' }
|
||||
if (
|
||||
/しない|しなく|せず|しないで|やめ|取り消|取消|ではなく|じゃなく|不要|送信|送って|送る/.test(
|
||||
text,
|
||||
)
|
||||
) {
|
||||
return {
|
||||
error: 'この内容は自動で変更できません。対象の画面で確認してください。',
|
||||
error: 'Please review this action in the relevant screen.',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +93,7 @@ export function interpretAction(
|
||||
if (route)
|
||||
return {
|
||||
action: { type: 'navigate', to: route },
|
||||
label: `${destination}を開く`,
|
||||
label: `Open ${{ '/support': 'Messages', '/journal': 'Journal', '/inbox': 'Reader', '/deck': 'Twitter Lite' }[route]}`,
|
||||
}
|
||||
|
||||
const note = text.match(/^(メモ|再開地点)[::\s]+(.+)$/s)
|
||||
@@ -71,7 +103,7 @@ export function interpretAction(
|
||||
if (!body) return unsupported
|
||||
return {
|
||||
action: { type: 'journal', kind, text: body },
|
||||
label: `${kind}を記録する`,
|
||||
label: `Save ${kind === 'メモ' ? 'note' : 'resume point'}`,
|
||||
}
|
||||
}
|
||||
const weight = text.match(
|
||||
@@ -80,7 +112,7 @@ export function interpretAction(
|
||||
if (weight?.[1])
|
||||
return {
|
||||
action: { type: 'journal', kind: '体重', text: weight[1] },
|
||||
label: `体重 ${weight[1]} kg を記録する`,
|
||||
label: `Log weight: ${weight[1]} kg`,
|
||||
}
|
||||
if (
|
||||
/^(?:薬を飲んだ|服薬した|服薬を記録)(?:ので記録して|と記録して)?$/.test(
|
||||
@@ -88,8 +120,8 @@ export function interpretAction(
|
||||
)
|
||||
) {
|
||||
return {
|
||||
action: { type: 'journal', kind: '服薬', text: '服薬した' },
|
||||
label: '服薬したことを記録する',
|
||||
action: { type: 'journal', kind: '服薬', text: 'Took medication' },
|
||||
label: 'Log medication',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +134,9 @@ export function interpretAction(
|
||||
(item) =>
|
||||
item.name === name ||
|
||||
item.name.replace(/\s/g, '') === name ||
|
||||
item.name.split(/\s+/)[0] === name,
|
||||
item.name
|
||||
.split(/\s+/)
|
||||
.some((part) => part.toLowerCase() === name.toLowerCase()),
|
||||
)
|
||||
const matchingTasks = tasks.filter((task) => task.title === name)
|
||||
const matchingTask = matchingTasks[0]
|
||||
@@ -118,28 +152,28 @@ export function interpretAction(
|
||||
id: matchingTask.id,
|
||||
status: 'later',
|
||||
},
|
||||
label: `${name}をあとでにする`,
|
||||
label: `Defer ${name}`,
|
||||
}
|
||||
}
|
||||
if (matches.length > 0 && matchingTasks.length > 0 && /保留/.test(text)) {
|
||||
return {
|
||||
error:
|
||||
'タスクと連絡先の両方に当てはまります。対象の画面で確認してください。',
|
||||
'This matches both a task and a contact. Please use the relevant screen.',
|
||||
}
|
||||
}
|
||||
const person = matches[0]
|
||||
if (matches.length !== 1 || !person)
|
||||
return { error: '連絡先をひとりに絞れません。氏名を入力してください。' }
|
||||
return { error: 'Please enter the full name of one contact.' }
|
||||
if (/相手待ち|保留/.test(text)) {
|
||||
const status = text.includes('相手待ち') ? 'waiting' : 'later'
|
||||
return {
|
||||
action: { type: 'contact-status', id: person.id, status },
|
||||
label: `${person.name}との連絡を${status === 'waiting' ? '相手待ち' : '保留'}にする`,
|
||||
label: `Mark the conversation with ${person.name} as ${status === 'waiting' ? 'waiting' : 'later'}`,
|
||||
}
|
||||
}
|
||||
return {
|
||||
action: { type: 'contact', id: person.id },
|
||||
label: `${person.name}への返信を確認する`,
|
||||
label: `Review reply to ${person.name}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,8 +184,7 @@ export function interpretAction(
|
||||
const task = matches[0]
|
||||
if (matches.length !== 1 || !task)
|
||||
return {
|
||||
error:
|
||||
'タスクをひとつに絞れません。一覧の名前をそのまま指定してください。',
|
||||
error: 'Please use the exact name of one task from the list.',
|
||||
}
|
||||
return {
|
||||
action: {
|
||||
@@ -159,7 +192,7 @@ export function interpretAction(
|
||||
id: task.id,
|
||||
direction: move[2] === '上' ? -1 : 1,
|
||||
},
|
||||
label: `${task.title}をひとつ${move[2]}に移動する`,
|
||||
label: `Move ${task.title} ${move[2] === '上' ? 'up' : 'down'}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,8 +209,7 @@ export function interpretAction(
|
||||
)
|
||||
if (matches.length + steps.length !== 1)
|
||||
return {
|
||||
error:
|
||||
'タスクをひとつに絞れません。一覧の名前をそのまま指定してください。',
|
||||
error: 'Please use the exact name of one task from the list.',
|
||||
}
|
||||
const status = /未完了|戻して/.test(change[2])
|
||||
? 'todo'
|
||||
@@ -187,7 +219,7 @@ export function interpretAction(
|
||||
const step = steps[0]
|
||||
if (steps.length === 1 && step) {
|
||||
if (status === 'later')
|
||||
return { error: 'ルーティンの手順は完了・未完了に変更できます。' }
|
||||
return { error: 'Routine steps can be marked done or incomplete.' }
|
||||
return {
|
||||
action: {
|
||||
type: 'routine-step',
|
||||
@@ -195,14 +227,14 @@ export function interpretAction(
|
||||
step: step.step,
|
||||
done: status === 'done',
|
||||
},
|
||||
label: `${title}を${status === 'done' ? '完了' : '未完了'}にする`,
|
||||
label: `Mark ${title} as ${status === 'done' ? 'done' : 'incomplete'}`,
|
||||
}
|
||||
}
|
||||
const task = matches[0]
|
||||
if (!task) return unsupported
|
||||
return {
|
||||
action: { type: 'task-status', id: task.id, status },
|
||||
label: `${title}を${status === 'done' ? '完了' : status === 'later' ? 'あとで' : '未完了'}にする`,
|
||||
label: `Mark ${title} as ${status === 'done' ? 'done' : status === 'later' ? 'later' : 'incomplete'}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +250,7 @@ export function interpretAction(
|
||||
const section = routine ? 'routine' : optional ? 'optional' : 'focus'
|
||||
return {
|
||||
action: { type: 'add-task', title, section },
|
||||
label: `「${title}」を${section === 'routine' ? 'ルーティン' : section === 'optional' ? '余裕があれば' : '今日のタスク'}に追加する`,
|
||||
label: `Add “${title}” to ${section === 'routine' ? 'Routines' : section === 'optional' ? 'If there’s room' : 'Start here'}`,
|
||||
}
|
||||
}
|
||||
return unsupported
|
||||
|
||||
Reference in New Issue
Block a user