@nexttylabs/echo 0.8.0 → 0.10.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @nexttylabs/echo
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fc3d8f1: support light/dark theme
8
+ - cc04d9f: Use the organization member role uniformly.
9
+ - 5cfdeb7: feat: support editing user profile
10
+
11
+ ### Patch Changes
12
+
13
+ - 76fda4d: fix organizational setting i18n
14
+ - 7dba561: remove legacy changeset files
15
+ - 158b254: show current orgnization
16
+ - 1e798f1: remove unuse tables and published files
17
+ - e3c4e1c: fix workflow errors
18
+ - daf9abb: first release
19
+
20
+ ## 0.9.0
21
+
22
+ ### Minor Changes
23
+
24
+ - fc3d8f1: support light/dark theme
25
+ - cc04d9f: Use the organization member role uniformly.
26
+ - 5cfdeb7: feat: support editing user profile
27
+
28
+ ### Patch Changes
29
+
30
+ - 76fda4d: fix organizational setting i18n
31
+ - 7dba561: remove legacy changeset files
32
+ - 158b254: show current orgnization
33
+ - 1e798f1: remove unuse tables and published files
34
+ - e3c4e1c: fix workflow errors
35
+ - daf9abb: first release
36
+
3
37
  ## 0.8.0
4
38
 
5
39
  ### Minor Changes
@@ -38,7 +38,7 @@ export default async function AccessSettingsPage() {
38
38
  <p className="text-muted-foreground">{t("pageDescription")}</p>
39
39
  </div>
40
40
 
41
- <Card className="border-slate-200/80 bg-white/80 shadow-sm">
41
+ <Card>
42
42
  <CardHeader>
43
43
  <CardTitle>{t("visibilityTitle")}</CardTitle>
44
44
  <CardDescription>{t("visibilityDesc")}</CardDescription>
@@ -17,6 +17,7 @@
17
17
 
18
18
  import { cookies, headers } from "next/headers";
19
19
  import { redirect } from "next/navigation";
20
+ import { getTranslations } from "next-intl/server";
20
21
  import { auth } from "@/lib/auth/config";
21
22
  import { db } from "@/lib/db";
22
23
  import { getUserOrganizations } from "@/lib/auth/organization";
@@ -27,11 +28,15 @@ import type { UserRole } from "@/lib/auth/permissions";
27
28
  import { organizationMembers, user } from "@/lib/db/schema";
28
29
  import { eq } from "drizzle-orm";
29
30
 
30
- export const metadata = {
31
- title: "组织管理 - Echo",
32
- };
31
+ export async function generateMetadata() {
32
+ const t = await getTranslations("settings.organizationPage");
33
+ return {
34
+ title: `${t("pageTitle")} - Echo`,
35
+ };
36
+ }
33
37
 
34
38
  export default async function OrganizationSettingsPage() {
39
+ const t = await getTranslations("settings.organizationPage");
35
40
  const session = await auth.api.getSession({ headers: await headers() });
36
41
 
37
42
  if (!session?.user) {
@@ -80,8 +85,8 @@ export default async function OrganizationSettingsPage() {
80
85
  return (
81
86
  <div className="max-w-4xl space-y-6">
82
87
  <div>
83
- <h1 className="text-2xl font-semibold">组织管理</h1>
84
- <p className="text-muted-foreground">管理您的组织信息和成员</p>
88
+ <h1 className="text-2xl font-semibold">{t("pageTitle")}</h1>
89
+ <p className="text-muted-foreground">{t("pageDescription")}</p>
85
90
  </div>
86
91
 
87
92
  <OrganizationForm
@@ -101,3 +106,4 @@ export default async function OrganizationSettingsPage() {
101
106
  </div>
102
107
  );
103
108
  }
109
+
@@ -20,6 +20,7 @@
20
20
 
21
21
  import { useState } from "react";
22
22
  import { useForm, useWatch } from "react-hook-form";
23
+ import { useTranslations } from "next-intl";
23
24
  import { Button } from "@/components/ui/button";
24
25
  import { Label } from "@/components/ui/label";
25
26
  import { Switch } from "@/components/ui/switch";
@@ -67,6 +68,7 @@ function SwitchField({
67
68
  }
68
69
 
69
70
  export function PortalAccessForm({ organizationId, initialSharing, initialSeo }: PortalAccessFormProps) {
71
+ const t = useTranslations("settings.access.form");
70
72
  const [saving, setSaving] = useState(false);
71
73
  const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
72
74
 
@@ -108,7 +110,7 @@ export function PortalAccessForm({ organizationId, initialSharing, initialSeo }:
108
110
  const sharingResult = await updatePortalSettings(organizationId, "sharing", sharingPayload);
109
111
 
110
112
  if (!sharingResult.success) {
111
- setMessage({ type: "error", text: sharingResult.error || "保存失败" });
113
+ setMessage({ type: "error", text: sharingResult.error || t("saveFailed") });
112
114
  setSaving(false);
113
115
  return;
114
116
  }
@@ -116,9 +118,9 @@ export function PortalAccessForm({ organizationId, initialSharing, initialSeo }:
116
118
  const seoResult = await updatePortalSettings(organizationId, "seo", seoPayload);
117
119
 
118
120
  if (seoResult.success) {
119
- setMessage({ type: "success", text: "可见性设置已保存" });
121
+ setMessage({ type: "success", text: t("saveSuccess") });
120
122
  } else {
121
- setMessage({ type: "error", text: seoResult.error || "保存失败" });
123
+ setMessage({ type: "error", text: seoResult.error || t("saveFailed") });
122
124
  }
123
125
 
124
126
  setSaving(false);
@@ -128,48 +130,48 @@ export function PortalAccessForm({ organizationId, initialSharing, initialSeo }:
128
130
  <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
129
131
  <SwitchField
130
132
  id="enabled"
131
- label="公开 Portal"
132
- description="关闭后访客无法访问公开门户"
133
+ label={t("enabled")}
134
+ description={t("enabledDesc")}
133
135
  checked={enabled}
134
136
  onCheckedChange={(checked) => form.setValue("enabled", checked)}
135
137
  />
136
138
 
137
139
  <SwitchField
138
140
  id="allowPublicVoting"
139
- label="允许公开投票"
140
- description="访客无需登录即可投票"
141
+ label={t("allowPublicVoting")}
142
+ description={t("allowPublicVotingDesc")}
141
143
  checked={allowPublicVoting}
142
144
  onCheckedChange={(checked) => form.setValue("allowPublicVoting", checked)}
143
145
  />
144
146
 
145
147
  <SwitchField
146
148
  id="allowPublicComments"
147
- label="允许公开评论"
148
- description="访客无需登录即可评论"
149
+ label={t("allowPublicComments")}
150
+ description={t("allowPublicCommentsDesc")}
149
151
  checked={allowPublicComments}
150
152
  onCheckedChange={(checked) => form.setValue("allowPublicComments", checked)}
151
153
  />
152
154
 
153
155
  <SwitchField
154
156
  id="showVoteCount"
155
- label="显示投票数量"
156
- description="在反馈列表中显示投票计数"
157
+ label={t("showVoteCount")}
158
+ description={t("showVoteCountDesc")}
157
159
  checked={showVoteCount}
158
160
  onCheckedChange={(checked) => form.setValue("showVoteCount", checked)}
159
161
  />
160
162
 
161
163
  <SwitchField
162
164
  id="showAuthor"
163
- label="显示提交者"
164
- description="显示反馈提交者名称"
165
+ label={t("showAuthor")}
166
+ description={t("showAuthorDesc")}
165
167
  checked={showAuthor}
166
168
  onCheckedChange={(checked) => form.setValue("showAuthor", checked)}
167
169
  />
168
170
 
169
171
  <SwitchField
170
172
  id="noIndex"
171
- label="阻止搜索引擎索引"
172
- description="启用后搜索引擎将不会收录此页面"
173
+ label={t("noIndex")}
174
+ description={t("noIndexDesc")}
173
175
  checked={noIndex}
174
176
  onCheckedChange={(checked) => form.setValue("noIndex", checked)}
175
177
  />
@@ -177,12 +179,12 @@ export function PortalAccessForm({ organizationId, initialSharing, initialSeo }:
177
179
  <div className="flex items-center gap-4">
178
180
  <Button type="submit" disabled={saving}>
179
181
  {saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
180
- 保存更改
182
+ {t("saveButton")}
181
183
  </Button>
182
184
  {message && (
183
185
  <p
184
186
  className={`text-sm ${
185
- message.type === "success" ? "text-green-600" : "text-destructive"
187
+ message.type === "success" ? "text-green-600 dark:text-green-400" : "text-destructive"
186
188
  }`}
187
189
  >
188
190
  {message.text}
@@ -192,3 +194,4 @@ export function PortalAccessForm({ organizationId, initialSharing, initialSeo }:
192
194
  </form>
193
195
  );
194
196
  }
197
+
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  import { useState } from "react";
22
+ import { useTranslations } from "next-intl";
22
23
  import { Button } from "@/components/ui/button";
23
24
  import {
24
25
  Card,
@@ -38,6 +39,7 @@ type InviteMemberFormProps = {
38
39
  };
39
40
 
40
41
  export function InviteMemberForm({ organizationId }: InviteMemberFormProps) {
42
+ const t = useTranslations("settings.organizationPage.invite");
41
43
  const [email, setEmail] = useState("");
42
44
  const [isLoading, setIsLoading] = useState(false);
43
45
  const [error, setError] = useState<string | null>(null);
@@ -58,12 +60,12 @@ export function InviteMemberForm({ organizationId }: InviteMemberFormProps) {
58
60
  const json = await res.json().catch(() => null);
59
61
 
60
62
  if (!res.ok) {
61
- setError(json?.error ?? "发送邀请失败,请稍后重试");
63
+ setError(json?.error ?? t("sendFailed"));
62
64
  setIsLoading(false);
63
65
  return;
64
66
  }
65
67
 
66
- setSuccess("邀请已发送,等待对方接受");
68
+ setSuccess(t("sendSuccess"));
67
69
  setEmail("");
68
70
  setIsLoading(false);
69
71
  };
@@ -71,8 +73,8 @@ export function InviteMemberForm({ organizationId }: InviteMemberFormProps) {
71
73
  return (
72
74
  <Card>
73
75
  <CardHeader>
74
- <CardTitle>邀请成员</CardTitle>
75
- <CardDescription>通过邮箱邀请新成员加入组织</CardDescription>
76
+ <CardTitle>{t("title")}</CardTitle>
77
+ <CardDescription>{t("description")}</CardDescription>
76
78
  </CardHeader>
77
79
  <CardContent>
78
80
  <form onSubmit={handleSubmit} className="space-y-4">
@@ -88,7 +90,7 @@ export function InviteMemberForm({ organizationId }: InviteMemberFormProps) {
88
90
  ) : null}
89
91
 
90
92
  <div className="space-y-2">
91
- <Label htmlFor="invite-email">成员邮箱</Label>
93
+ <Label htmlFor="invite-email">{t("emailLabel")}</Label>
92
94
  <Input
93
95
  id="invite-email"
94
96
  name="email"
@@ -102,18 +104,19 @@ export function InviteMemberForm({ organizationId }: InviteMemberFormProps) {
102
104
  </div>
103
105
 
104
106
  <div className="space-y-2">
105
- <Label>邀请角色</Label>
107
+ <Label>{t("roleLabel")}</Label>
106
108
  <div className="flex items-center gap-3 rounded-md border border-dashed border-slate-200 px-3 py-2 text-sm text-slate-600">
107
109
  <Badge variant="secondary">{DEFAULT_ROLE}</Badge>
108
- <span>当前仅支持成员角色</span>
110
+ <span>{t("roleNote")}</span>
109
111
  </div>
110
112
  </div>
111
113
 
112
114
  <Button type="submit" className="w-full" disabled={isLoading || !email}>
113
- {isLoading ? "发送中..." : "发送邀请"}
115
+ {isLoading ? t("sending") : t("send")}
114
116
  </Button>
115
117
  </form>
116
118
  </CardContent>
117
119
  </Card>
118
120
  );
119
121
  }
122
+
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  import { useState } from "react";
22
+ import { useTranslations } from "next-intl";
22
23
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
23
24
  import { Button } from "@/components/ui/button";
24
25
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@@ -47,6 +48,7 @@ export function OrganizationMembersList({
47
48
  currentUserId,
48
49
  initialMembers,
49
50
  }: OrganizationMembersListProps) {
51
+ const t = useTranslations("settings.organizationPage.members");
50
52
  const [members, setMembers] = useState(initialMembers);
51
53
  const [error, setError] = useState<string | null>(null);
52
54
  const [pendingMemberId, setPendingMemberId] = useState<string | null>(null);
@@ -68,7 +70,7 @@ export function OrganizationMembersList({
68
70
 
69
71
  const json = await res.json().catch(() => null);
70
72
  if (!res.ok) {
71
- setError(json?.error ?? "移除失败,请稍后重试");
73
+ setError(json?.error ?? t("removeFailed"));
72
74
  setPendingMemberId(null);
73
75
  return;
74
76
  }
@@ -93,7 +95,7 @@ export function OrganizationMembersList({
93
95
 
94
96
  const json = await res.json().catch(() => null);
95
97
  if (!res.ok) {
96
- setError(json?.error ?? "更新角色失败,请稍后重试");
98
+ setError(json?.error ?? t("updateRoleFailed"));
97
99
  setPendingMemberId(null);
98
100
  return;
99
101
  }
@@ -110,8 +112,8 @@ export function OrganizationMembersList({
110
112
  <>
111
113
  <Card>
112
114
  <CardHeader>
113
- <CardTitle>组织成员</CardTitle>
114
- <CardDescription>查看并移除组织中的成员</CardDescription>
115
+ <CardTitle>{t("title")}</CardTitle>
116
+ <CardDescription>{t("description")}</CardDescription>
115
117
  </CardHeader>
116
118
  <CardContent className="space-y-4">
117
119
  {error ? (
@@ -122,14 +124,14 @@ export function OrganizationMembersList({
122
124
 
123
125
  {members.length === 0 ? (
124
126
  <div className="rounded-md border border-dashed border-slate-200 bg-white/60 px-4 py-6 text-sm text-slate-500">
125
- 当前组织暂无成员。
127
+ {t("noMembers")}
126
128
  </div>
127
129
  ) : (
128
130
  <div className="space-y-3">
129
131
  {members.map((member) => {
130
132
  const isSelf = currentUserId === member.userId;
131
133
  const isPending = pendingMemberId === member.userId;
132
- const displayName = member.name ?? member.email ?? "未知成员";
134
+ const displayName = member.name ?? member.email ?? t("unknownMember");
133
135
  const secondaryText = member.email ?? member.userId;
134
136
 
135
137
  return (
@@ -151,7 +153,7 @@ export function OrganizationMembersList({
151
153
 
152
154
  {isSelf ? (
153
155
  <Button variant="outline" size="sm" disabled>
154
- 不能移除自己
156
+ {t("cannotRemoveSelf")}
155
157
  </Button>
156
158
  ) : (
157
159
  <Button
@@ -160,7 +162,7 @@ export function OrganizationMembersList({
160
162
  disabled={!currentUserId || isPending}
161
163
  onClick={() => setMemberToRemove({ id: member.userId, name: displayName })}
162
164
  >
163
- {isPending ? "移除中..." : "移除"}
165
+ {isPending ? t("removing") : t("remove")}
164
166
  </Button>
165
167
  )}
166
168
  </div>
@@ -175,19 +177,19 @@ export function OrganizationMembersList({
175
177
  <AlertDialog open={!!memberToRemove} onOpenChange={(open) => !open && setMemberToRemove(null)}>
176
178
  <AlertDialogContent>
177
179
  <AlertDialogHeader>
178
- <AlertDialogTitle>确认移除成员?</AlertDialogTitle>
180
+ <AlertDialogTitle>{t("confirmRemoveTitle")}</AlertDialogTitle>
179
181
  <AlertDialogDescription>
180
- {memberToRemove?.name} 将失去组织访问权限,此操作无法撤销。
182
+ {t("confirmRemoveDescription", { name: memberToRemove?.name ?? "" })}
181
183
  </AlertDialogDescription>
182
184
  </AlertDialogHeader>
183
185
  <AlertDialogFooter>
184
- <AlertDialogCancel disabled={pendingMemberId !== null}>取消</AlertDialogCancel>
186
+ <AlertDialogCancel disabled={pendingMemberId !== null}>{t("cancel")}</AlertDialogCancel>
185
187
  <AlertDialogAction
186
188
  variant="destructive"
187
189
  onClick={handleRemove}
188
190
  disabled={pendingMemberId !== null}
189
191
  >
190
- {pendingMemberId === memberToRemove?.id ? "移除中..." : "确认移除"}
192
+ {pendingMemberId === memberToRemove?.id ? t("removing") : t("confirmRemove")}
191
193
  </AlertDialogAction>
192
194
  </AlertDialogFooter>
193
195
  </AlertDialogContent>
@@ -195,3 +197,4 @@ export function OrganizationMembersList({
195
197
  </>
196
198
  );
197
199
  }
200
+
package/messages/en.json CHANGED
@@ -126,6 +126,36 @@
126
126
  "updatedSuccess": "Organization updated",
127
127
  "genericError": "Something went wrong. Please try again later."
128
128
  },
129
+ "organizationPage": {
130
+ "pageTitle": "Organization Management",
131
+ "pageDescription": "Manage your organization information and members",
132
+ "members": {
133
+ "title": "Organization Members",
134
+ "description": "View and manage members in the organization",
135
+ "noMembers": "No members in this organization yet.",
136
+ "unknownMember": "Unknown Member",
137
+ "removeFailed": "Failed to remove member, please try again later",
138
+ "updateRoleFailed": "Failed to update role, please try again later",
139
+ "cannotRemoveSelf": "Cannot remove yourself",
140
+ "removing": "Removing...",
141
+ "remove": "Remove",
142
+ "confirmRemoveTitle": "Confirm member removal?",
143
+ "confirmRemoveDescription": "{name} will lose access to the organization. This action cannot be undone.",
144
+ "cancel": "Cancel",
145
+ "confirmRemove": "Confirm Remove"
146
+ },
147
+ "invite": {
148
+ "title": "Invite Members",
149
+ "description": "Invite new members to join the organization via email",
150
+ "emailLabel": "Member Email",
151
+ "roleLabel": "Invite Role",
152
+ "roleNote": "Currently only member role is supported",
153
+ "sendFailed": "Failed to send invitation, please try again later",
154
+ "sendSuccess": "Invitation sent, waiting for acceptance",
155
+ "sending": "Sending...",
156
+ "send": "Send Invitation"
157
+ }
158
+ },
129
159
  "profile": {
130
160
  "pageTitle": "Profile",
131
161
  "pageDescription": "Manage your account information and preferences",
@@ -243,7 +273,24 @@
243
273
  "pageTitle": "Organization Access",
244
274
  "pageDescription": "Control public access and permissions",
245
275
  "visibilityTitle": "Visibility & Permissions",
246
- "visibilityDesc": "Configure public access, voting, and indexing settings"
276
+ "visibilityDesc": "Configure public access, voting, and indexing settings",
277
+ "form": {
278
+ "enabled": "Public Portal",
279
+ "enabledDesc": "When disabled, visitors cannot access the public portal",
280
+ "allowPublicVoting": "Allow Public Voting",
281
+ "allowPublicVotingDesc": "Visitors can vote without logging in",
282
+ "allowPublicComments": "Allow Public Comments",
283
+ "allowPublicCommentsDesc": "Visitors can comment without logging in",
284
+ "showVoteCount": "Show Vote Count",
285
+ "showVoteCountDesc": "Display vote count in the feedback list",
286
+ "showAuthor": "Show Author",
287
+ "showAuthorDesc": "Display feedback submitter name",
288
+ "noIndex": "Block Search Engine Indexing",
289
+ "noIndexDesc": "When enabled, search engines will not index this page",
290
+ "saveButton": "Save Changes",
291
+ "saveSuccess": "Visibility settings saved",
292
+ "saveFailed": "Save failed"
293
+ }
247
294
  },
248
295
  "branding": {
249
296
  "pageTitle": "Branding",
package/messages/jp.json CHANGED
@@ -126,6 +126,36 @@
126
126
  "updatedSuccess": "組織情報を更新しました",
127
127
  "genericError": "エラーが発生しました。後でもう一度お試しください。"
128
128
  },
129
+ "organizationPage": {
130
+ "pageTitle": "組織管理",
131
+ "pageDescription": "組織情報とメンバーを管理します",
132
+ "members": {
133
+ "title": "組織メンバー",
134
+ "description": "組織内のメンバーを表示・管理",
135
+ "noMembers": "現在この組織にはメンバーがいません。",
136
+ "unknownMember": "不明なメンバー",
137
+ "removeFailed": "メンバーの削除に失敗しました。後でもう一度お試しください",
138
+ "updateRoleFailed": "ロールの更新に失敗しました。後でもう一度お試しください",
139
+ "cannotRemoveSelf": "自分自身を削除できません",
140
+ "removing": "削除中...",
141
+ "remove": "削除",
142
+ "confirmRemoveTitle": "メンバーを削除しますか?",
143
+ "confirmRemoveDescription": "{name} は組織へのアクセス権を失います。この操作は取り消せません。",
144
+ "cancel": "キャンセル",
145
+ "confirmRemove": "削除を確認"
146
+ },
147
+ "invite": {
148
+ "title": "メンバーを招待",
149
+ "description": "メールでメンバーを組織に招待",
150
+ "emailLabel": "メンバーのメールアドレス",
151
+ "roleLabel": "招待ロール",
152
+ "roleNote": "現在はメンバーロールのみサポートされています",
153
+ "sendFailed": "招待の送信に失敗しました。後でもう一度お試しください",
154
+ "sendSuccess": "招待を送信しました。相手の承認をお待ちください",
155
+ "sending": "送信中...",
156
+ "send": "招待を送信"
157
+ }
158
+ },
129
159
  "profile": {
130
160
  "pageTitle": "プロフィール",
131
161
  "pageDescription": "アカウント情報と設定を管理",
@@ -243,7 +273,24 @@
243
273
  "pageTitle": "組織アクセス",
244
274
  "pageDescription": "公開アクセスと権限を制御",
245
275
  "visibilityTitle": "可視性と権限",
246
- "visibilityDesc": "公開アクセス、投票、インデックス設定を構成"
276
+ "visibilityDesc": "公開アクセス、投票、インデックス設定を構成",
277
+ "form": {
278
+ "enabled": "ポータルを公開",
279
+ "enabledDesc": "無効にすると、訪問者は公開ポータルにアクセスできません",
280
+ "allowPublicVoting": "公開投票を許可",
281
+ "allowPublicVotingDesc": "訪問者はログインせずに投票できます",
282
+ "allowPublicComments": "公開コメントを許可",
283
+ "allowPublicCommentsDesc": "訪問者はログインせずにコメントできます",
284
+ "showVoteCount": "投票数を表示",
285
+ "showVoteCountDesc": "フィードバック一覧に投票数を表示します",
286
+ "showAuthor": "投稿者を表示",
287
+ "showAuthorDesc": "フィードバック投稿者の名前を表示します",
288
+ "noIndex": "検索エンジンのインデックスをブロック",
289
+ "noIndexDesc": "有効にすると、検索エンジンはこのページをインデックスしません",
290
+ "saveButton": "変更を保存",
291
+ "saveSuccess": "可視性設定を保存しました",
292
+ "saveFailed": "保存に失敗しました"
293
+ }
247
294
  },
248
295
  "branding": {
249
296
  "pageTitle": "ブランディング",
@@ -126,6 +126,36 @@
126
126
  "updatedSuccess": "组织信息已更新",
127
127
  "genericError": "发生错误,请稍后重试"
128
128
  },
129
+ "organizationPage": {
130
+ "pageTitle": "组织管理",
131
+ "pageDescription": "管理您的组织信息和成员",
132
+ "members": {
133
+ "title": "组织成员",
134
+ "description": "查看并移除组织中的成员",
135
+ "noMembers": "当前组织暂无成员。",
136
+ "unknownMember": "未知成员",
137
+ "removeFailed": "移除失败,请稍后重试",
138
+ "updateRoleFailed": "更新角色失败,请稍后重试",
139
+ "cannotRemoveSelf": "不能移除自己",
140
+ "removing": "移除中...",
141
+ "remove": "移除",
142
+ "confirmRemoveTitle": "确认移除成员?",
143
+ "confirmRemoveDescription": "{name} 将失去组织访问权限,此操作无法撤销。",
144
+ "cancel": "取消",
145
+ "confirmRemove": "确认移除"
146
+ },
147
+ "invite": {
148
+ "title": "邀请成员",
149
+ "description": "通过邮箱邀请新成员加入组织",
150
+ "emailLabel": "成员邮箱",
151
+ "roleLabel": "邀请角色",
152
+ "roleNote": "当前仅支持成员角色",
153
+ "sendFailed": "发送邀请失败,请稍后重试",
154
+ "sendSuccess": "邀请已发送,等待对方接受",
155
+ "sending": "发送中...",
156
+ "send": "发送邀请"
157
+ }
158
+ },
129
159
  "profile": {
130
160
  "pageTitle": "个人资料",
131
161
  "pageDescription": "管理你的账户信息和偏好设置",
@@ -243,7 +273,24 @@
243
273
  "pageTitle": "组织访问",
244
274
  "pageDescription": "控制公开访问和权限",
245
275
  "visibilityTitle": "可见性与权限",
246
- "visibilityDesc": "配置公开访问、投票和索引设置"
276
+ "visibilityDesc": "配置公开访问、投票和索引设置",
277
+ "form": {
278
+ "enabled": "公开 Portal",
279
+ "enabledDesc": "关闭后访客无法访问公开门户",
280
+ "allowPublicVoting": "允许公开投票",
281
+ "allowPublicVotingDesc": "访客无需登录即可投票",
282
+ "allowPublicComments": "允许公开评论",
283
+ "allowPublicCommentsDesc": "访客无需登录即可评论",
284
+ "showVoteCount": "显示投票数量",
285
+ "showVoteCountDesc": "在反馈列表中显示投票计数",
286
+ "showAuthor": "显示提交者",
287
+ "showAuthorDesc": "显示反馈提交者名称",
288
+ "noIndex": "阻止搜索引擎索引",
289
+ "noIndexDesc": "启用后搜索引擎将不会收录此页面",
290
+ "saveButton": "保存更改",
291
+ "saveSuccess": "可见性设置已保存",
292
+ "saveFailed": "保存失败"
293
+ }
247
294
  },
248
295
  "branding": {
249
296
  "pageTitle": "品牌",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexttylabs/echo",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "license": "AGPL-3.0",
5
5
  "private": false,
6
6
  "publishConfig": {