mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-23 08:55:44 +08:00
feat(i18n): localize backend messages by workspace language
This commit is contained in:
@@ -51,6 +51,17 @@ const ASSIGNABLE_ROLES: Exclude<WorkspaceRole, 'owner'>[] = [
|
||||
'viewer',
|
||||
];
|
||||
|
||||
const MESSAGE_LANGUAGES: Record<string, string> = {
|
||||
en_US: 'English',
|
||||
zh_Hans: '简体中文',
|
||||
zh_Hant: '繁體中文',
|
||||
ja_JP: '日本語',
|
||||
es_ES: 'Español',
|
||||
ru_RU: 'Русский',
|
||||
th_TH: 'ไทย',
|
||||
vi_VN: 'Tiếng Việt',
|
||||
};
|
||||
|
||||
export default function WorkspaceSettingsPanel({
|
||||
active,
|
||||
}: WorkspaceSettingsPanelProps) {
|
||||
@@ -61,6 +72,9 @@ export default function WorkspaceSettingsPanel({
|
||||
const [members, setMembers] = useState<WorkspaceMembership[]>([]);
|
||||
const [invitations, setInvitations] = useState<WorkspaceInvitation[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [messageLanguage, setMessageLanguage] = useState('en_US');
|
||||
const [supportedLanguages, setSupportedLanguages] = useState<string[]>([]);
|
||||
const [savingLanguage, setSavingLanguage] = useState(false);
|
||||
const [inviteEmail, setInviteEmail] = useState('');
|
||||
const [inviteRole, setInviteRole] =
|
||||
useState<Exclude<WorkspaceRole, 'owner'>>('viewer');
|
||||
@@ -86,8 +100,13 @@ export default function WorkspaceSettingsPanel({
|
||||
const loadWorkspace = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const current = await backendClient.getCurrentWorkspace();
|
||||
const [current, languageSettings] = await Promise.all([
|
||||
backendClient.getCurrentWorkspace(),
|
||||
backendClient.getWorkspaceLanguage(),
|
||||
]);
|
||||
setWorkspaceInfo(current);
|
||||
setMessageLanguage(languageSettings.language);
|
||||
setSupportedLanguages(languageSettings.supported_languages);
|
||||
|
||||
const [memberResponse, invitationResponse] = await Promise.all([
|
||||
current.permissions.includes('member.view')
|
||||
@@ -106,6 +125,19 @@ export default function WorkspaceSettingsPanel({
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
async function saveLanguage(locale: string) {
|
||||
setSavingLanguage(true);
|
||||
try {
|
||||
const result = await backendClient.setWorkspaceLanguage(locale);
|
||||
setMessageLanguage(result.language);
|
||||
toast.success(t('workspaceLanguage.saved'));
|
||||
} catch {
|
||||
toast.error(t('workspaceLanguage.saveFailed'));
|
||||
} finally {
|
||||
setSavingLanguage(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (active) void loadWorkspace();
|
||||
}, [active, loadWorkspace]);
|
||||
@@ -222,6 +254,41 @@ export default function WorkspaceSettingsPanel({
|
||||
</PanelToolbar>
|
||||
|
||||
<PanelBody className="space-y-6">
|
||||
<section className="space-y-3">
|
||||
<div>
|
||||
<h3 id="workspace-language-label" className="text-sm font-semibold">
|
||||
{t('workspaceLanguage.label')}
|
||||
</h3>
|
||||
<p
|
||||
id="workspace-language-description"
|
||||
className="text-xs text-muted-foreground"
|
||||
>
|
||||
{t('workspaceLanguage.description')}
|
||||
</p>
|
||||
</div>
|
||||
<Select
|
||||
value={messageLanguage}
|
||||
onValueChange={saveLanguage}
|
||||
disabled={
|
||||
loading || savingLanguage || !permissions.has('workspace.update')
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="w-full sm:w-64"
|
||||
aria-labelledby="workspace-language-label"
|
||||
aria-describedby="workspace-language-description"
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{supportedLanguages.map((locale) => (
|
||||
<SelectItem key={locale} value={locale}>
|
||||
{MESSAGE_LANGUAGES[locale] || locale}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</section>
|
||||
{canInvite && (
|
||||
<section className="space-y-3">
|
||||
<div>
|
||||
|
||||
@@ -111,6 +111,17 @@ import type {
|
||||
* 负责与后端 API 的所有交互
|
||||
*/
|
||||
export class BackendClient extends BaseHttpClient {
|
||||
public getWorkspaceLanguage(): Promise<{
|
||||
language: string;
|
||||
supported_languages: string[];
|
||||
}> {
|
||||
return this.get('/api/v1/workspaces/current/language');
|
||||
}
|
||||
|
||||
public setWorkspaceLanguage(language: string): Promise<{ language: string }> {
|
||||
return this.put('/api/v1/workspaces/current/language', { language });
|
||||
}
|
||||
|
||||
constructor(baseURL: string) {
|
||||
super(baseURL, false);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from './workspaceContext';
|
||||
import { setCurrentWorkspaceSnapshot } from './currentWorkspaceStore';
|
||||
import { clearWorkspaceBootstrapSnapshot } from './workspaceBootstrapStore';
|
||||
import i18n from 'i18next';
|
||||
|
||||
type JSONValue = string | number | boolean | JSONObject | JSONArray | null;
|
||||
interface JSONObject {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import slackSetup from './slack-setup/en-US';
|
||||
import pipelineMigration from './pipeline-migration/en-US';
|
||||
const enUS = {
|
||||
workspaceLanguage: {
|
||||
label: 'Workspace language',
|
||||
description:
|
||||
'Language for backend messages and default bot replies. Your interface language stays independent.',
|
||||
saved: 'Language saved',
|
||||
saveFailed: 'Failed to save language',
|
||||
},
|
||||
slackSetup,
|
||||
inflight: {
|
||||
autoCollapse: 'Auto-collapse',
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import pipelineMigration from './pipeline-migration/es-ES';
|
||||
const esES = {
|
||||
workspaceLanguage: {
|
||||
label: 'Idioma del espacio de trabajo',
|
||||
description:
|
||||
'Idioma de los mensajes del servidor y respuestas predeterminadas del bot. No cambia tu interfaz.',
|
||||
saved: 'Idioma guardado',
|
||||
saveFailed: 'No se pudo guardar el idioma',
|
||||
},
|
||||
inflight: {
|
||||
autoCollapse: 'Plegar auto.',
|
||||
empty: 'No hay eventos en proceso',
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import slackSetup from './slack-setup/ja-JP';
|
||||
import pipelineMigration from './pipeline-migration/ja-JP';
|
||||
const jaJP = {
|
||||
workspaceLanguage: {
|
||||
label: 'ワークスペースの言語',
|
||||
description:
|
||||
'バックエンドの通知とボットの既定の返信に使用します。個人の表示言語には影響しません。',
|
||||
saved: '言語設定を保存しました',
|
||||
saveFailed: '言語設定の保存に失敗しました',
|
||||
},
|
||||
slackSetup,
|
||||
inflight: {
|
||||
autoCollapse: '自動折りたたみ',
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import pipelineMigration from './pipeline-migration/ru-RU';
|
||||
const ruRU = {
|
||||
workspaceLanguage: {
|
||||
label: 'Язык рабочего пространства',
|
||||
description:
|
||||
'Язык сообщений сервера и стандартных ответов бота. Язык интерфейса не меняется.',
|
||||
saved: 'Язык сохранён',
|
||||
saveFailed: 'Не удалось сохранить язык',
|
||||
},
|
||||
inflight: {
|
||||
autoCollapse: 'Автосворачивание',
|
||||
empty: 'Нет событий в обработке',
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import pipelineMigration from './pipeline-migration/th-TH';
|
||||
const thTH = {
|
||||
workspaceLanguage: {
|
||||
label: 'ภาษาของพื้นที่ทำงาน',
|
||||
description:
|
||||
'ใช้สำหรับข้อความจากระบบและคำตอบเริ่มต้นของบอต โดยไม่เปลี่ยนภาษาอินเทอร์เฟซของคุณ',
|
||||
saved: 'บันทึกภาษาแล้ว',
|
||||
saveFailed: 'บันทึกภาษาไม่สำเร็จ',
|
||||
},
|
||||
inflight: {
|
||||
autoCollapse: 'ยุบอัตโนมัติ',
|
||||
empty: 'ไม่มีเหตุการณ์ที่กำลังประมวลผล',
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import pipelineMigration from './pipeline-migration/vi-VN';
|
||||
const viVN = {
|
||||
workspaceLanguage: {
|
||||
label: 'Ngôn ngữ không gian làm việc',
|
||||
description:
|
||||
'Dùng cho thông báo máy chủ và phản hồi mặc định của bot. Không thay đổi ngôn ngữ giao diện cá nhân.',
|
||||
saved: 'Đã lưu ngôn ngữ',
|
||||
saveFailed: 'Không thể lưu ngôn ngữ',
|
||||
},
|
||||
inflight: {
|
||||
autoCollapse: 'Tự thu gọn',
|
||||
empty: 'Không có sự kiện đang xử lý',
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import slackSetup from './slack-setup/zh-Hans';
|
||||
import pipelineMigration from './pipeline-migration/zh-Hans';
|
||||
const zhHans = {
|
||||
workspaceLanguage: {
|
||||
label: '工作区语言',
|
||||
description: '用于后端提示和机器人的默认回复,不影响个人界面语言。',
|
||||
saved: '语言设置已保存',
|
||||
saveFailed: '语言设置保存失败',
|
||||
},
|
||||
slackSetup,
|
||||
inflight: {
|
||||
autoCollapse: '自动折叠',
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import slackSetup from './slack-setup/zh-Hant';
|
||||
import pipelineMigration from './pipeline-migration/zh-Hant';
|
||||
const zhHant = {
|
||||
workspaceLanguage: {
|
||||
label: '工作區語言',
|
||||
description: '用於後端提示和機器人的預設回覆,不影響個人介面語言。',
|
||||
saved: '語言設定已儲存',
|
||||
saveFailed: '語言設定儲存失敗',
|
||||
},
|
||||
slackSetup,
|
||||
inflight: {
|
||||
autoCollapse: '自動摺疊',
|
||||
|
||||
Reference in New Issue
Block a user