@agent-native/core 0.98.7 → 0.98.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/docs/content/external-agents.mdx +17 -11
  4. package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +6 -2
  5. package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +6 -2
  6. package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +6 -2
  7. package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +6 -2
  8. package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +6 -2
  9. package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +6 -2
  10. package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +6 -2
  11. package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +6 -2
  12. package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +6 -2
  13. package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +6 -2
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/client/session-replay.ts +29 -7
  16. package/corpus/core/src/integrations/adapters/slack.ts +13 -2
  17. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +2 -1
  18. package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
  19. package/corpus/core/src/integrations/plugin.ts +183 -48
  20. package/corpus/core/src/integrations/types.ts +7 -0
  21. package/corpus/core/src/mcp/build-server.ts +99 -26
  22. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +31 -2
  23. package/corpus/templates/calendar/AGENTS.md +11 -0
  24. package/corpus/templates/calendar/README.md +2 -1
  25. package/corpus/templates/calendar/actions/create-event.ts +7 -6
  26. package/corpus/templates/calendar/actions/event-action-helpers.ts +49 -0
  27. package/corpus/templates/calendar/actions/update-event.ts +225 -14
  28. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +185 -70
  29. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +26 -4
  30. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +112 -69
  31. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +621 -524
  32. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +275 -164
  33. package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +222 -0
  34. package/corpus/templates/calendar/app/hooks/use-events.ts +151 -79
  35. package/corpus/templates/calendar/app/i18n/zh-TW.ts +6 -0
  36. package/corpus/templates/calendar/app/i18n-data.ts +60 -0
  37. package/corpus/templates/calendar/app/lib/all-day-layout.ts +126 -0
  38. package/corpus/templates/calendar/app/lib/event-form-utils.ts +18 -1
  39. package/corpus/templates/calendar/app/lib/event-mutation-inputs.ts +1 -1
  40. package/corpus/templates/calendar/app/lib/working-location.ts +163 -0
  41. package/corpus/templates/calendar/app/pages/CalendarView.tsx +21 -2
  42. package/corpus/templates/calendar/changelog/2026-07-07-working-locations-from-google-calendar-now-appear-as-native-.md +6 -0
  43. package/corpus/templates/calendar/server/lib/calendar-availability.ts +2 -0
  44. package/corpus/templates/calendar/server/lib/google-api.ts +6 -1
  45. package/corpus/templates/calendar/server/lib/google-calendar.ts +49 -19
  46. package/corpus/templates/calendar/shared/api.ts +2 -0
  47. package/dist/client/session-replay.d.ts.map +1 -1
  48. package/dist/client/session-replay.js +24 -7
  49. package/dist/client/session-replay.js.map +1 -1
  50. package/dist/collab/routes.d.ts +2 -2
  51. package/dist/integrations/adapters/slack.js +11 -2
  52. package/dist/integrations/adapters/slack.js.map +1 -1
  53. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  54. package/dist/integrations/pending-tasks-retry-job.js +2 -1
  55. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  56. package/dist/integrations/pending-tasks-store.js +1 -1
  57. package/dist/integrations/pending-tasks-store.js.map +1 -1
  58. package/dist/integrations/plugin.d.ts.map +1 -1
  59. package/dist/integrations/plugin.js +140 -34
  60. package/dist/integrations/plugin.js.map +1 -1
  61. package/dist/integrations/types.d.ts +7 -0
  62. package/dist/integrations/types.d.ts.map +1 -1
  63. package/dist/integrations/types.js.map +1 -1
  64. package/dist/mcp/build-server.d.ts.map +1 -1
  65. package/dist/mcp/build-server.js +82 -28
  66. package/dist/mcp/build-server.js.map +1 -1
  67. package/dist/notifications/routes.d.ts +1 -1
  68. package/dist/observability/routes.d.ts +5 -5
  69. package/dist/secrets/routes.d.ts +9 -9
  70. package/docs/content/external-agents.mdx +17 -11
  71. package/docs/content/locales/ar-SA/external-agents.mdx +6 -2
  72. package/docs/content/locales/de-DE/external-agents.mdx +6 -2
  73. package/docs/content/locales/es-ES/external-agents.mdx +6 -2
  74. package/docs/content/locales/fr-FR/external-agents.mdx +6 -2
  75. package/docs/content/locales/hi-IN/external-agents.mdx +6 -2
  76. package/docs/content/locales/ja-JP/external-agents.mdx +6 -2
  77. package/docs/content/locales/ko-KR/external-agents.mdx +6 -2
  78. package/docs/content/locales/pt-BR/external-agents.mdx +6 -2
  79. package/docs/content/locales/zh-CN/external-agents.mdx +6 -2
  80. package/docs/content/locales/zh-TW/external-agents.mdx +6 -2
  81. package/package.json +1 -1
@@ -0,0 +1,222 @@
1
+ import { useT } from "@agent-native/core/client";
2
+ import type { CalendarEvent } from "@shared/api";
3
+ import { IconBuilding, IconHome, IconMapPin } from "@tabler/icons-react";
4
+ import { useEffect, useMemo, useState } from "react";
5
+
6
+ import { Button } from "@/components/ui/button";
7
+ import { Input } from "@/components/ui/input";
8
+ import { Label } from "@/components/ui/label";
9
+ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
10
+ import { cn } from "@/lib/utils";
11
+ import {
12
+ createWorkingLocationDisplayLabels,
13
+ getWorkingLocationDetail,
14
+ getWorkingLocationEditableLabel,
15
+ getWorkingLocationLabel,
16
+ getWorkingLocationType,
17
+ type WorkingLocationKind,
18
+ type WorkingLocationSelection,
19
+ } from "@/lib/working-location";
20
+
21
+ interface WorkingLocationEditorProps {
22
+ event: CalendarEvent;
23
+ isRecurring: boolean;
24
+ readOnly?: boolean;
25
+ disabled?: boolean;
26
+ onSave: (selection: WorkingLocationSelection) => void;
27
+ }
28
+
29
+ type LabeledWorkingLocation = Exclude<WorkingLocationKind, "homeOffice">;
30
+
31
+ function initialLabels(event: CalendarEvent) {
32
+ const type = getWorkingLocationType(event);
33
+ const editableLabel = getWorkingLocationEditableLabel(event);
34
+ return {
35
+ officeLocation: type === "officeLocation" ? editableLabel : "",
36
+ customLocation: type === "customLocation" ? editableLabel : "",
37
+ } satisfies Record<LabeledWorkingLocation, string>;
38
+ }
39
+
40
+ export function WorkingLocationEditor({
41
+ event,
42
+ isRecurring,
43
+ readOnly = false,
44
+ disabled = false,
45
+ onSave,
46
+ }: WorkingLocationEditorProps) {
47
+ const t = useT();
48
+ const workingLocationLabels = createWorkingLocationDisplayLabels(t);
49
+ const currentType = getWorkingLocationType(event);
50
+ const currentLabel = getWorkingLocationEditableLabel(event);
51
+ const [type, setType] = useState<WorkingLocationKind>(currentType);
52
+ const [labels, setLabels] = useState(() => initialLabels(event));
53
+ const [scope, setScope] = useState<"single" | "all">("single");
54
+ const detail = getWorkingLocationDetail(event, workingLocationLabels);
55
+
56
+ useEffect(() => {
57
+ setType(getWorkingLocationType(event));
58
+ setLabels(initialLabels(event));
59
+ setScope("single");
60
+ }, [currentLabel, currentType, event.id]);
61
+
62
+ const label = type === "homeOffice" ? "" : labels[type];
63
+ const isValid = type === "homeOffice" || label.trim().length > 0;
64
+ const isDirty = useMemo(
65
+ () => type !== currentType || label.trim() !== currentLabel.trim(),
66
+ [currentLabel, currentType, label, type],
67
+ );
68
+
69
+ if (readOnly) {
70
+ return (
71
+ <div className="flex items-start gap-3 py-1.5">
72
+ <IconMapPin className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
73
+ <div className="min-w-0">
74
+ <div className="truncate text-sm text-foreground">
75
+ {getWorkingLocationLabel(event, workingLocationLabels)}
76
+ </div>
77
+ <div className="truncate text-xs text-muted-foreground">
78
+ {detail || t("eventForm.workingLocation")}
79
+ </div>
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ const options: Array<{
86
+ value: WorkingLocationKind;
87
+ label: string;
88
+ icon: typeof IconHome;
89
+ }> = [
90
+ { value: "homeOffice", label: t("eventForm.home"), icon: IconHome },
91
+ {
92
+ value: "officeLocation",
93
+ label: t("eventForm.office"),
94
+ icon: IconBuilding,
95
+ },
96
+ {
97
+ value: "customLocation",
98
+ label: t("eventForm.other"),
99
+ icon: IconMapPin,
100
+ },
101
+ ];
102
+
103
+ return (
104
+ <div className="py-1.5" data-working-location-editor>
105
+ <div className="mb-2 flex items-center gap-2 text-sm font-medium text-foreground">
106
+ <IconMapPin className="size-4 text-muted-foreground" />
107
+ {t("eventForm.workingLocation")}
108
+ </div>
109
+ <RadioGroup
110
+ value={type}
111
+ onValueChange={(value) => setType(value as WorkingLocationKind)}
112
+ className="grid grid-cols-3 gap-1.5"
113
+ aria-label={t("eventForm.workingLocation")}
114
+ disabled={disabled}
115
+ >
116
+ {options.map((option) => {
117
+ const Icon = option.icon;
118
+ const id = `working-location-${event.id}-${option.value}`;
119
+ return (
120
+ <div key={option.value} className="relative">
121
+ <RadioGroupItem
122
+ id={id}
123
+ value={option.value}
124
+ className="peer sr-only"
125
+ />
126
+ <Label
127
+ htmlFor={id}
128
+ className={cn(
129
+ "flex h-8 cursor-pointer items-center justify-center gap-1 rounded-md border border-border px-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground peer-data-[state=checked]:border-foreground/40 peer-data-[state=checked]:bg-muted peer-data-[state=checked]:text-foreground",
130
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2",
131
+ disabled && "cursor-not-allowed opacity-50",
132
+ )}
133
+ >
134
+ <Icon className="size-3.5" />
135
+ <span className="truncate">{option.label}</span>
136
+ </Label>
137
+ </div>
138
+ );
139
+ })}
140
+ </RadioGroup>
141
+
142
+ {type !== "homeOffice" && (
143
+ <div className="mt-2">
144
+ <Input
145
+ value={labels[type]}
146
+ onChange={(event) =>
147
+ setLabels((current) => ({
148
+ ...current,
149
+ [type]: event.target.value,
150
+ }))
151
+ }
152
+ aria-label={
153
+ type === "officeLocation"
154
+ ? t("eventForm.office")
155
+ : t("eventForm.other")
156
+ }
157
+ placeholder={t("eventForm.addLocation")}
158
+ className="h-8 text-sm"
159
+ disabled={disabled}
160
+ />
161
+ {type === "officeLocation" && detail && (
162
+ <p className="mt-1 truncate text-xs text-muted-foreground">
163
+ {detail}
164
+ </p>
165
+ )}
166
+ </div>
167
+ )}
168
+
169
+ {isRecurring && (
170
+ <div className="mt-2">
171
+ <p className="mb-1 text-[10px] font-medium text-muted-foreground">
172
+ {t("eventForm.applyTo")}
173
+ </p>
174
+ <RadioGroup
175
+ value={scope}
176
+ onValueChange={(value) => setScope(value as "single" | "all")}
177
+ className="grid grid-cols-2 gap-1.5"
178
+ aria-label={t("eventForm.applyTo")}
179
+ disabled={disabled}
180
+ >
181
+ {(
182
+ [
183
+ ["single", t("eventForm.thisDayOnly")],
184
+ ["all", t("eventForm.allDays")],
185
+ ] as const
186
+ ).map(([value, text]) => {
187
+ const id = `working-location-scope-${event.id}-${value}`;
188
+ return (
189
+ <div key={value} className="relative">
190
+ <RadioGroupItem
191
+ id={id}
192
+ value={value}
193
+ className="peer sr-only"
194
+ />
195
+ <Label
196
+ htmlFor={id}
197
+ className="flex h-7 cursor-pointer items-center justify-center rounded-md border border-border px-2 text-center text-xs font-medium text-muted-foreground transition-colors hover:bg-muted/50 peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-data-[state=checked]:border-foreground/40 peer-data-[state=checked]:bg-muted peer-data-[state=checked]:text-foreground"
198
+ >
199
+ {text}
200
+ </Label>
201
+ </div>
202
+ );
203
+ })}
204
+ </RadioGroup>
205
+ </div>
206
+ )}
207
+
208
+ <div className="mt-2 flex justify-end">
209
+ <Button
210
+ size="sm"
211
+ className="h-7 text-xs"
212
+ disabled={disabled || !isValid || !isDirty}
213
+ onClick={() =>
214
+ onSave({ type, label, scope: isRecurring ? scope : undefined })
215
+ }
216
+ >
217
+ {t("eventForm.save")}
218
+ </Button>
219
+ </div>
220
+ </div>
221
+ );
222
+ }
@@ -11,6 +11,12 @@ import {
11
11
  } from "@tanstack/react-query";
12
12
  import { nanoid } from "nanoid";
13
13
 
14
+ import {
15
+ buildWorkingLocationProperties,
16
+ getWorkingLocationEditableLabel,
17
+ getWorkingLocationType,
18
+ } from "@/lib/working-location";
19
+
14
20
  import {
15
21
  applyCalendarEventRsvp,
16
22
  calendarEventOverlapsListParams,
@@ -37,6 +43,8 @@ type UpdateEventInput = Partial<CalendarEvent> & {
37
43
  sendUpdates?: "all" | "none";
38
44
  notificationMessage?: string;
39
45
  scope?: UpdateEventScope;
46
+ workingLocationType?: "homeOffice" | "officeLocation" | "customLocation";
47
+ workingLocationLabel?: string;
40
48
  };
41
49
 
42
50
  type EventListSnapshot = Array<[QueryKey, CalendarEvent[] | undefined]>;
@@ -48,6 +56,14 @@ type RsvpEventMutationContext = EventListMutationContext & {
48
56
  previousEvent?: CalendarEvent;
49
57
  };
50
58
 
59
+ type UpdateEventResult = Partial<CalendarEvent> & {
60
+ id?: string;
61
+ replacedId?: string;
62
+ success?: boolean;
63
+ updated?: string[];
64
+ message?: string;
65
+ };
66
+
51
67
  const LIST_EVENTS_QUERY_KEY = ["action", "list-events"] as const;
52
68
  const OPTIMISTIC_EVENT_PREFIX = "optimistic_event_";
53
69
 
@@ -148,6 +164,16 @@ export function mergeAttendeeLists(
148
164
  return Array.from(merged.values());
149
165
  }
150
166
 
167
+ export function shouldDeferOptimisticEventUpdate(
168
+ target: CalendarEvent | undefined,
169
+ hasWorkingLocationUpdate: boolean,
170
+ ): boolean {
171
+ return (
172
+ hasWorkingLocationUpdate ||
173
+ (target?.eventType === "workingLocation" && target.allDay === true)
174
+ );
175
+ }
176
+
151
177
  function updateListEventQueries(
152
178
  queryClient: ReturnType<typeof useQueryClient>,
153
179
  updater: (
@@ -293,90 +319,136 @@ export function useCreateEvent() {
293
319
 
294
320
  export function useUpdateEvent() {
295
321
  const queryClient = useQueryClient();
296
- return useActionMutation<
297
- Partial<CalendarEvent> & {
298
- id?: string;
299
- success?: boolean;
300
- updated?: string[];
301
- message?: string;
302
- },
303
- UpdateEventInput
304
- >("update-event", {
305
- onMutate: async (newData) => {
306
- await queryClient.cancelQueries({ queryKey: ["action", "list-events"] });
307
- const previous = queryClient.getQueriesData<CalendarEvent[]>({
308
- queryKey: ["action", "list-events"],
309
- });
310
- const {
311
- addGoogleMeet,
312
- addZoom,
313
- addAttendees,
314
- sendUpdates,
315
- notificationMessage,
316
- scope,
317
- ...optimisticData
318
- } = newData;
319
- queryClient.setQueriesData<CalendarEvent[]>(
320
- { queryKey: ["action", "list-events"] },
321
- (old) =>
322
- old?.map((e) =>
323
- e.id === optimisticData.id
324
- ? {
325
- ...e,
326
- ...optimisticData,
327
- ...(addAttendees
328
- ? {
329
- attendees: mergeAttendeeLists(
330
- e.attendees,
331
- addAttendees,
332
- ),
333
- }
334
- : {}),
335
- }
336
- : e,
337
- ),
338
- );
339
- return { previous };
340
- },
341
- onSuccess: (updated) => {
342
- const eventPatch = updated as
343
- | (Partial<CalendarEvent> & {
344
- id?: string;
345
- success?: boolean;
346
- updated?: string[];
347
- message?: string;
348
- })
349
- | undefined;
350
- if (!eventPatch?.id) return;
351
- const {
352
- success: _success,
353
- updated: _updated,
354
- message: _message,
355
- ...data
356
- } = eventPatch;
357
- queryClient.setQueriesData<CalendarEvent[]>(
358
- { queryKey: ["action", "list-events"] },
359
- (old) =>
360
- old?.map((event) =>
361
- event.id === eventPatch.id ? { ...event, ...data } : event,
362
- ),
363
- );
364
- },
365
- onError: (_err, _newData, context) => {
366
- const previous = (context as EventListMutationContext | undefined)
367
- ?.previous;
368
- if (previous) {
369
- for (const [key, data] of previous) {
370
- queryClient.setQueryData(key, data);
322
+ return useActionMutation<UpdateEventResult, UpdateEventInput>(
323
+ "update-event",
324
+ {
325
+ onMutate: async (newData) => {
326
+ await queryClient.cancelQueries({
327
+ queryKey: ["action", "list-events"],
328
+ });
329
+ const previous = queryClient.getQueriesData<CalendarEvent[]>({
330
+ queryKey: ["action", "list-events"],
331
+ });
332
+ const {
333
+ addGoogleMeet,
334
+ addZoom,
335
+ addAttendees,
336
+ sendUpdates,
337
+ notificationMessage,
338
+ scope,
339
+ workingLocationType,
340
+ workingLocationLabel,
341
+ ...optimisticData
342
+ } = newData;
343
+ const hasWorkingLocationUpdate =
344
+ workingLocationType !== undefined ||
345
+ workingLocationLabel !== undefined;
346
+ queryClient.setQueriesData<CalendarEvent[]>(
347
+ { queryKey: ["action", "list-events"] },
348
+ (old) => {
349
+ const target = old?.find((event) => event.id === optimisticData.id);
350
+ // A working-location change can alter the visual grouping of several
351
+ // days. Keep the editor mounted until Google confirms the write so a
352
+ // rejected request does not appear to save and then snap back.
353
+ if (
354
+ shouldDeferOptimisticEventUpdate(target, hasWorkingLocationUpdate)
355
+ )
356
+ return old;
357
+ return old?.map((e) => {
358
+ const matchesScope =
359
+ e.id === optimisticData.id ||
360
+ (scope === "all" &&
361
+ !!target?.recurringEventId &&
362
+ e.recurringEventId === target.recurringEventId);
363
+ if (!matchesScope) return e;
364
+ const nextWorkingLocationType =
365
+ workingLocationType ?? getWorkingLocationType(e);
366
+ const nextWorkingLocationLabel =
367
+ workingLocationLabel ?? getWorkingLocationEditableLabel(e);
368
+ return {
369
+ ...e,
370
+ ...optimisticData,
371
+ ...(hasWorkingLocationUpdate
372
+ ? {
373
+ workingLocationProperties: buildWorkingLocationProperties(
374
+ e,
375
+ {
376
+ type: nextWorkingLocationType,
377
+ label: nextWorkingLocationLabel,
378
+ },
379
+ ),
380
+ }
381
+ : {}),
382
+ ...(addAttendees
383
+ ? {
384
+ attendees: mergeAttendeeLists(e.attendees, addAttendees),
385
+ }
386
+ : {}),
387
+ };
388
+ });
389
+ },
390
+ );
391
+ return { previous };
392
+ },
393
+ onSuccess: (updated, input) => {
394
+ const eventPatch = updated as UpdateEventResult | undefined;
395
+ if (!eventPatch?.id) return;
396
+ queryClient.setQueriesData<CalendarEvent[]>(
397
+ { queryKey: ["action", "list-events"] },
398
+ (old) => reconcileUpdatedEventList(old, input.id, eventPatch),
399
+ );
400
+ },
401
+ onError: (_err, _newData, context) => {
402
+ const previous = (context as EventListMutationContext | undefined)
403
+ ?.previous;
404
+ if (previous) {
405
+ for (const [key, data] of previous) {
406
+ queryClient.setQueryData(key, data);
407
+ }
371
408
  }
372
- }
373
- },
374
- onSettled: () => {
375
- queryClient.invalidateQueries({ queryKey: ["action", "list-events"] });
409
+ },
410
+ onSettled: () => {
411
+ queryClient.invalidateQueries({ queryKey: ["action", "list-events"] });
412
+ },
376
413
  },
414
+ );
415
+ }
416
+
417
+ export function reconcileUpdatedEventList(
418
+ events: CalendarEvent[] | undefined,
419
+ originalId: string,
420
+ result: UpdateEventResult,
421
+ ): CalendarEvent[] | undefined {
422
+ if (!result.id) return events;
423
+ const {
424
+ success: _success,
425
+ updated: _updated,
426
+ message: _message,
427
+ replacedId,
428
+ ...eventPatch
429
+ } = result;
430
+ const targetId = replacedId ?? originalId;
431
+
432
+ return events?.map((event) => {
433
+ if (event.id !== targetId) return event;
434
+ const idChanged = event.id !== eventPatch.id;
435
+ return {
436
+ ...event,
437
+ ...eventPatch,
438
+ ...(idChanged ? { _replacedId: event.id } : {}),
439
+ };
377
440
  });
378
441
  }
379
442
 
443
+ export function findEventByCurrentOrReplacedId(
444
+ events: CalendarEvent[],
445
+ eventId: string,
446
+ ): CalendarEvent | undefined {
447
+ return events.find(
448
+ (event) => event.id === eventId || event._replacedId === eventId,
449
+ );
450
+ }
451
+
380
452
  export function useDeleteEvent() {
381
453
  const queryClient = useQueryClient();
382
454
  return useActionMutation<
@@ -672,6 +672,7 @@ const messages = {
672
672
  addingMeetingLink: "新增 {{provider}} 連結",
673
673
  alerts: "警報",
674
674
  allDay: "全天",
675
+ allDays: "所有日期",
675
676
  allEvents: "所有活動",
676
677
  applyChangesTo: "將更改應用到",
677
678
  applyTo: "適用於",
@@ -752,6 +753,7 @@ const messages = {
752
753
  notifyGuestsQuestion: "通知來賓?",
753
754
  none: "無",
754
755
  office: "辦公室",
756
+ other: "其他",
755
757
  openInSidebar: "在側邊欄中開啟",
756
758
  openInGoogleCalendar: "在 Google Calendar 中開啟",
757
759
  optionalDescription: "可選描述",
@@ -803,6 +805,7 @@ const messages = {
803
805
  start: "開始",
804
806
  startDate: "開始日期",
805
807
  subscribedFeeds: "已訂閱的來源",
808
+ thisDayOnly: "僅限當天",
806
809
  thisEvent: "本次活動",
807
810
  timeSelected: "選定時間",
808
811
  timezone: "時區",
@@ -826,6 +829,9 @@ const messages = {
826
829
  weekly: "每週",
827
830
  workingFrom: "工作自",
828
831
  workingLocation: "工作地點",
832
+ workingLocationDesk: "座位 {{desk}}",
833
+ workingLocationFloor: "樓層 {{floor}}",
834
+ workingLocationTitle: "工作地點:{{location}}",
829
835
  yearly: "每年",
830
836
  zoom: "Zoom",
831
837
  zoomAdded: "新增Zoom",