feat(i18n): add zh_Hant and ja_JP translations to all adapter YAML files

- Add zh_Hant (Traditional Chinese) to all 17 adapter YAML metadata and config fields
- Add ja_JP translations to global adapters (Telegram, Discord, Slack, Lark, LINE)
- Fix buggy zh_Hant in line.yaml and slack.yaml (contained simplified Chinese)
- Add zh_Hant field to backend I18nString model
- Add adapter category grouping with locale-aware ordering
- Add webhook Cloud CTA for community edition users
- Fix wizard progress not clearing on skip/complete
This commit is contained in:
Junyan Qin
2026-03-28 19:41:27 +08:00
parent 71e44f0e54
commit 99e2976826
28 changed files with 612 additions and 214 deletions
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
IChooseAdapterEntity,
IPipelineEntity,
@@ -35,6 +35,7 @@ import {
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
@@ -47,6 +48,10 @@ import {
} from '@/components/ui/card';
import { extractI18nObject } from '@/i18n/I18nProvider';
import { CustomApiError } from '@/app/infra/entities/common';
import {
groupByCategory,
getCategoryLabel,
} from '@/app/infra/entities/adapter-categories';
const getFormSchema = (t: (key: string) => string) =>
z.object({
@@ -113,6 +118,12 @@ export default function BotForm({
const currentAdapter = form.watch('adapter');
const currentAdapterConfig = form.watch('adapter_config');
// Group adapters by category for the Select dropdown
const groupedAdapters = useMemo(
() => groupByCategory(adapterNameList),
[adapterNameList],
);
// Notify parent when dirty state changes
const { isDirty } = form.formState;
useEffect(() => {
@@ -183,6 +194,7 @@ export default function BotForm({
return {
label: extractI18nObject(item.label),
value: item.name,
categories: item.spec.categories,
};
}),
);
@@ -483,20 +495,31 @@ export default function BotForm({
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>
{adapterNameList.map((item) => (
<SelectItem key={item.value} value={item.value}>
<div className="flex items-center gap-2">
<img
src={httpClient.getAdapterIconURL(item.value)}
alt=""
className="h-5 w-5 rounded"
/>
<span>{item.label}</span>
</div>
</SelectItem>
))}
</SelectGroup>
{groupedAdapters.map((group) => (
<SelectGroup
key={group.categoryId ?? 'uncategorized'}
>
{group.categoryId && (
<SelectLabel>
{getCategoryLabel(t, group.categoryId)}
</SelectLabel>
)}
{group.items.map((item) => (
<SelectItem key={item.value} value={item.value}>
<div className="flex items-center gap-2">
<img
src={httpClient.getAdapterIconURL(
item.value,
)}
alt=""
className="h-5 w-5 rounded"
/>
<span>{item.label}</span>
</div>
</SelectItem>
))}
</SelectGroup>
))}
</SelectContent>
</Select>
</FormControl>
@@ -1,6 +1,7 @@
export interface IChooseAdapterEntity {
label: string;
value: string;
categories?: string[];
}
export interface IPipelineEntity {