mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-23 00:21:28 +00:00
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:
parent
c73b642504
commit
7e45b0a09c
12 changed files with 114 additions and 40 deletions
|
|
@ -259,4 +259,62 @@ describe("dispatchEvent fallback routing", () => {
|
|||
expect(sent.filter((u) => u.includes("/222/"))).toHaveLength(1);
|
||||
expect(sent.filter((u) => u.includes("/111/"))).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("uses the group's message language (not the route's)", async () => {
|
||||
const bodies: string[] = [];
|
||||
mockFetch((url, init) => {
|
||||
bodies.push(String(init?.body ?? ""));
|
||||
return new Response("{}", { status: 200 });
|
||||
});
|
||||
const kv = createMockKV();
|
||||
await kv.put(
|
||||
"config:groups",
|
||||
JSON.stringify([
|
||||
{ id: "zh-group", name: "中文组", adminIds: [], lang: "zh" },
|
||||
{ id: "en-group", name: "English", adminIds: [] },
|
||||
]),
|
||||
);
|
||||
const env = createEnv({ KV: kv, DB: createMockDB() });
|
||||
const routes: Route[] = [
|
||||
{
|
||||
id: "zh-push",
|
||||
name: "ZH",
|
||||
enabled: true,
|
||||
groupId: "zh-group",
|
||||
filters: [{ type: "event", match: "push" }],
|
||||
targets: [{ channelId: "111" }],
|
||||
},
|
||||
{
|
||||
id: "en-push",
|
||||
name: "EN",
|
||||
enabled: true,
|
||||
groupId: "en-group",
|
||||
filters: [{ type: "event", match: "push" }],
|
||||
targets: [{ channelId: "222" }],
|
||||
},
|
||||
];
|
||||
|
||||
await dispatchEvent(
|
||||
{ ...baseConfig, routes },
|
||||
{
|
||||
event: "push",
|
||||
payload: {
|
||||
repository: { full_name: "owner/repo" },
|
||||
commits: [{ id: "abc", message: "fix", author: { name: "a" } }],
|
||||
ref: "refs/heads/main",
|
||||
compare: "https://example.com/compare",
|
||||
},
|
||||
},
|
||||
env,
|
||||
);
|
||||
|
||||
expect(bodies).toHaveLength(2);
|
||||
const titles = bodies.map((b) => {
|
||||
const parsed = JSON.parse(b) as { embeds?: Array<{ title?: string }> };
|
||||
return parsed.embeds?.[0]?.title ?? "";
|
||||
});
|
||||
expect(titles.sort()).toEqual(
|
||||
["owner/repo: 推送了 1 个提交", "owner/repo: Pushed 1 commit"].sort(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ import { getDriver } from "../drivers";
|
|||
import type { SendResult } from "../drivers/types";
|
||||
|
||||
export async function dispatchEvent(config: Config, event: WebhookEvent, env: Env): Promise<void> {
|
||||
const langs = [...new Set(config.routes.map((r) => r.lang ?? "en"))];
|
||||
const groups = await loadGroups(env.KV);
|
||||
const groupById = new Map(groups.map((g) => [g.id, g]));
|
||||
|
||||
// Message language is configured per group (Group.lang), not per route.
|
||||
const langs = [...new Set(groups.map((g) => g.lang ?? "en"))];
|
||||
const trMap = new Map<string, Translations>();
|
||||
await Promise.all(
|
||||
langs.map(async (lang) => {
|
||||
|
|
@ -17,8 +21,6 @@ export async function dispatchEvent(config: Config, event: WebhookEvent, env: En
|
|||
}),
|
||||
);
|
||||
|
||||
const groups = await loadGroups(env.KV);
|
||||
const groupById = new Map(groups.map((g) => [g.id, g]));
|
||||
const owners = eventOwners(event);
|
||||
|
||||
const accepted = (route: Route): boolean => {
|
||||
|
|
@ -53,8 +55,8 @@ export async function dispatchEvent(config: Config, event: WebhookEvent, env: En
|
|||
const targets = route.targets && route.targets.length > 0 ? route.targets : [];
|
||||
if (targets.length === 0) return;
|
||||
|
||||
const tr = trMap.get(route.lang ?? "en")!;
|
||||
const group = route.groupId ? groupById.get(route.groupId) : undefined;
|
||||
const tr = trMap.get(group?.lang ?? "en")!;
|
||||
const showEmoji = group?.emoji !== false;
|
||||
const message = formatEvent(route, event, tr, showEmoji);
|
||||
if (route.discordRoleIds?.length) {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ export interface Route {
|
|||
enabled: boolean;
|
||||
filters: Filter[];
|
||||
targets: RouteTarget[];
|
||||
lang?: string;
|
||||
groupId?: string;
|
||||
/**
|
||||
* Fallback route: only fires when no other (non-fallback) route matched the
|
||||
|
|
@ -120,6 +119,11 @@ export interface Group {
|
|||
* Defaults to true when omitted.
|
||||
*/
|
||||
emoji?: boolean;
|
||||
/**
|
||||
* Message language for every route in this group (e.g. "en", "zh"; custom
|
||||
* via KV i18n:<lang>). Defaults to "en" when omitted.
|
||||
*/
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
|
|
|
|||
|
|
@ -87,9 +87,6 @@ function validateRoutes(
|
|||
}
|
||||
if (typeof r.enabled !== "boolean")
|
||||
return { ok: false, error: `route "${r.id}".enabled must be boolean` };
|
||||
if (r.lang !== undefined && typeof r.lang !== "string") {
|
||||
return { ok: false, error: `route "${r.id}".lang must be a string` };
|
||||
}
|
||||
if (r.fallback !== undefined && typeof r.fallback !== "boolean") {
|
||||
return { ok: false, error: `route "${r.id}".fallback must be a boolean` };
|
||||
}
|
||||
|
|
@ -283,6 +280,9 @@ function validateGroups(
|
|||
if (g.emoji !== undefined && typeof g.emoji !== "boolean") {
|
||||
return { ok: false, error: `group "${g.id}".emoji must be a boolean` };
|
||||
}
|
||||
if (g.lang !== undefined && typeof g.lang !== "string") {
|
||||
return { ok: false, error: `group "${g.id}".lang must be a string` };
|
||||
}
|
||||
}
|
||||
return { ok: true, groups: groups as Group[] };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue