feat(groups): configure message language per group instead of per route

Route.lang is removed; Group.lang now drives the message language for
every route in the group (default en, custom via KV i18n:<lang>).
dispatch.ts loads translations per group and falls back to en for
groups without a lang. Admin UI: GroupEditor gains the language field,
RouteEditor/RouteCard drop theirs; docs and config example updated.
This commit is contained in:
RhenCloud 2026-08-12 00:43:47 +08:00
parent c73b642504
commit 7e45b0a09c
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
12 changed files with 114 additions and 40 deletions

View file

@ -32,6 +32,27 @@
<div class="hint">{{ t("groupEditor.idHint") }}</div>
</div>
</div>
<div class="row2">
<div class="field">
<label>{{ t("groupEditor.language") }}</label>
<input
v-model="form.lang"
type="text"
:placeholder="t('groupEditor.langPlaceholder')"
/>
<div class="hint">{{ t("groupEditor.langHint") }}</div>
</div>
<div class="field">
<label
>{{ t("groupEditor.emoji") }}
<span class="lbl-note">{{ t("groupEditor.emojiNote") }}</span></label
>
<label class="inline">
<input v-model="form.emoji" type="checkbox" />
<span>{{ t("groupEditor.emojiLabel") }}</span>
</label>
</div>
</div>
<div class="field">
<label
>{{ t("groupEditor.membersNote") }}
@ -85,10 +106,6 @@
</div>
<div class="hint">{{ t("groupEditor.providersHint") }}</div>
</div>
<label class="inline">
<input v-model="form.emoji" type="checkbox" />
<span>{{ t("groupEditor.emoji") }}</span>
</label>
<div class="err">{{ formError }}</div>
</form>
<div class="editor-foot">
@ -126,6 +143,7 @@ const form = reactive({
owners: "",
providers: [] as ("github" | "gitea")[],
emoji: true,
lang: "",
});
function splitList(text: string): string[] {
@ -154,6 +172,7 @@ watch(
(p): p is "github" | "gitea" => p === "github" || p === "gitea",
);
form.emoji = g?.emoji ?? true;
form.lang = g?.lang ?? "";
formError.value = "";
},
);
@ -188,6 +207,7 @@ function save(): void {
owners: props.superAdmin ? (owners.length ? owners : undefined) : props.group?.owners,
providers: form.providers.length ? form.providers : undefined,
emoji: form.emoji,
lang: form.lang.trim() || undefined,
});
}
</script>

View file

@ -9,7 +9,6 @@
<span v-if="readonly" class="dot" :class="route.enabled ? 'ok' : 'bad'"></span>
<span class="route-name">{{ route.name || t("route.untitled") }}</span>
<span class="route-id">{{ route.id }}</span>
<span v-if="route.lang" class="badge lang">{{ route.lang }}</span>
<span
v-for="(tg, i) in route.targets"
:key="i"

View file

@ -37,21 +37,10 @@
required
/>
</div>
<div class="row2">
<div class="field">
<label>{{ t("routeEditor.id") }}</label>
<input v-model="form.id" type="text" placeholder="my-route" required />
<div class="hint">{{ t("routeEditor.idHint") }}</div>
</div>
<div class="field">
<label>{{ t("routeEditor.language") }}</label>
<input
v-model="form.lang"
type="text"
:placeholder="t('routeEditor.langPlaceholder')"
/>
<div class="hint">{{ t("routeEditor.langHint") }}</div>
</div>
<div class="field">
<label>{{ t("routeEditor.id") }}</label>
<input v-model="form.id" type="text" placeholder="my-route" required />
<div class="hint">{{ t("routeEditor.idHint") }}</div>
</div>
<div class="field inline">
<input v-model="form.enabled" type="checkbox" />
@ -211,7 +200,6 @@ function blankTarget(): TargetForm {
const form = reactive({
id: "",
name: "",
lang: "",
enabled: true,
fallback: false,
stop: false,
@ -263,7 +251,6 @@ watch(
const r = props.route;
form.id = r?.id ?? "";
form.name = r?.name ?? "";
form.lang = r?.lang ?? "";
form.enabled = r?.enabled ?? true;
form.fallback = r?.fallback ?? false;
form.stop = r?.stop ?? false;
@ -340,7 +327,6 @@ function collect(): Route | null {
enabled: form.enabled,
fallback: form.fallback || undefined,
stop: form.stop || undefined,
lang: form.lang.trim() || undefined,
discordRoleIds: discordRoles.length ? discordRoles : undefined,
filters,
targets,

View file

@ -99,9 +99,6 @@ const en: Dict = {
"routeEditor.namePlaceholder": "My Route",
"routeEditor.id": "ID",
"routeEditor.idHint": "Unique, use a-z / 0-9 / dashes",
"routeEditor.language": "Language",
"routeEditor.langPlaceholder": "en",
"routeEditor.langHint": "en or zh; custom via KV i18n:<lang>",
"routeEditor.enabled": "Route enabled",
"routeEditor.fallback": "Fallback route",
"routeEditor.fallbackHint": "Only fires when no other route matched; filters still apply",
@ -148,6 +145,11 @@ const en: Dict = {
"groupEditor.id": "ID",
"groupEditor.idPlaceholder": "my-team",
"groupEditor.idHint": "Unique, use a-z / 0-9 / dashes",
"groupEditor.language": "Message language",
"groupEditor.langPlaceholder": "en",
"groupEditor.langHint": "en or zh; custom via KV i18n:<lang>",
"groupEditor.emojiNote": "(default on)",
"groupEditor.emojiLabel": "Show emojis in messages",
"groupEditor.admins": "Group admins",
"groupEditor.adminsNote": "(GitHub login or user id, comma-separated)",
"groupEditor.adminsPlaceholder": "octocat, 12345",
@ -314,9 +316,6 @@ const zh: Dict = {
"routeEditor.namePlaceholder": "我的路由",
"routeEditor.id": "ID",
"routeEditor.idHint": "唯一,使用 a-z / 0-9 / 短横线",
"routeEditor.language": "语言",
"routeEditor.langPlaceholder": "zh",
"routeEditor.langHint": "en 或 zh可通过 KV i18n:<lang> 自定义",
"routeEditor.enabled": "启用路由",
"routeEditor.fallback": "兜底路由",
"routeEditor.fallbackHint": "仅当没有其它路由匹配该事件时才发送,其过滤器仍会生效",
@ -362,6 +361,11 @@ const zh: Dict = {
"groupEditor.id": "ID",
"groupEditor.idPlaceholder": "my-team",
"groupEditor.idHint": "唯一,使用 a-z / 0-9 / 短横线",
"groupEditor.language": "消息语言",
"groupEditor.langPlaceholder": "zh",
"groupEditor.langHint": "en 或 zh可通过 KV i18n:<lang> 自定义",
"groupEditor.emojiNote": "(默认开启)",
"groupEditor.emojiLabel": "消息中显示表情符号",
"groupEditor.admins": "分组管理员",
"groupEditor.adminsNote": "GitHub 登录名或用户 ID逗号分隔",
"groupEditor.adminsPlaceholder": "octocat, 12345",

View file

@ -18,7 +18,6 @@ export interface Route {
enabled: boolean;
filters: Filter[];
targets: RouteTarget[];
lang?: string;
groupId?: string;
fallback?: boolean;
stop?: boolean;
@ -40,6 +39,7 @@ export interface Group {
owners?: string[];
providers?: ("github" | "gitea" | "gitlab")[];
emoji?: boolean;
lang?: string;
}
export interface GroupInvite {