mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
frontend: apply more i18n (#2639)
This commit is contained in:
@@ -62,7 +62,7 @@ function ChatInput({ disabled = false, onSendMessage }: ChatInputProps) {
|
||||
? "cursor-not-allowed border-neutral-400 text-neutral-400"
|
||||
: "hover:bg-neutral-500 ",
|
||||
)}
|
||||
aria-label="Send message"
|
||||
aria-label={t(I18nKey.CHAT_INTERFACE$TOOLTIP_SEND_MESSAGE)}
|
||||
>
|
||||
<VscArrowUp />
|
||||
</button>
|
||||
|
||||
@@ -2,8 +2,10 @@ import React, { useState } from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import { FaClipboard } from "react-icons/fa";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { code } from "../markdown/code";
|
||||
import toast from "#/utils/toast";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface MessageProps {
|
||||
message: Message;
|
||||
@@ -18,14 +20,18 @@ function ChatMessage({ message }: MessageProps) {
|
||||
message.sender === "user" ? "bg-neutral-700 self-end" : "bg-neutral-500",
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard
|
||||
.writeText(message.content)
|
||||
.then(() => {
|
||||
toast.info("Message copied to clipboard!");
|
||||
toast.info(t(I18nKey.CHAT_INTERFACE$CHAT_MESSAGE_COPIED));
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error("copy-error", `Failed to copy message: ${error}`);
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
"copy-error",
|
||||
t(I18nKey.CHAT_INTERFACE$CHAT_MESSAGE_COPY_FAILED),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -40,7 +46,7 @@ function ChatMessage({ message }: MessageProps) {
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="absolute top-1 right-1 p-1 bg-neutral-600 rounded hover:bg-neutral-500 transition-opacity opacity-75 hover:opacity-100"
|
||||
aria-label="Copy message"
|
||||
aria-label={t(I18nKey.CHAT_INTERFACE$TOOLTIP_COPY_MESSAGE)}
|
||||
type="button"
|
||||
>
|
||||
<FaClipboard />
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "react-icons/io";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { IoFileTray } from "react-icons/io5";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import AgentState from "#/types/AgentState";
|
||||
import { setRefreshID } from "#/state/codeSlice";
|
||||
@@ -15,6 +16,7 @@ import IconButton from "../IconButton";
|
||||
import ExplorerTree from "./ExplorerTree";
|
||||
import toast from "#/utils/toast";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface ExplorerActionsProps {
|
||||
onRefresh: () => void;
|
||||
@@ -92,7 +94,7 @@ function FileExplorer() {
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const selectFileInput = () => {
|
||||
fileInputRef.current?.click(); // Trigger the file browser
|
||||
};
|
||||
@@ -113,7 +115,7 @@ function FileExplorer() {
|
||||
await uploadFiles(toAdd);
|
||||
await refreshWorkspace();
|
||||
} catch (error) {
|
||||
toast.error("ws", "Error uploading file");
|
||||
toast.error("ws", t(I18nKey.EXPLORER$UPLOAD_ERROR_MESSAGE));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,7 +160,9 @@ function FileExplorer() {
|
||||
className="z-10 absolute flex flex-col justify-center items-center bg-black top-0 bottom-0 left-0 right-0 opacity-65"
|
||||
>
|
||||
<IoFileTray size={32} />
|
||||
<p className="font-bold text-xl">Drop Files Here</p>
|
||||
<p className="font-bold text-xl">
|
||||
{t(I18nKey.EXPLORER$LABEL_DROP_FILES)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
@@ -176,7 +180,7 @@ function FileExplorer() {
|
||||
>
|
||||
{!isHidden && (
|
||||
<div className="ml-1 text-neutral-300 font-bold text-sm">
|
||||
Workspace
|
||||
{t(I18nKey.EXPLORER$LABEL_WORKSPACE)}
|
||||
</div>
|
||||
)}
|
||||
<ExplorerActions
|
||||
|
||||
@@ -58,7 +58,7 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
|
||||
setModels(await fetchModels());
|
||||
setAgents(await fetchAgents());
|
||||
} catch (error) {
|
||||
toast.error("settings", "Failed to fetch models and agents");
|
||||
toast.error("settings", t(I18nKey.CONFIGURATION$ERROR_FETCH_MODELS));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -268,6 +268,46 @@
|
||||
"en": "Please stop the agent before editing these settings.",
|
||||
"de": "Bitte beenden Sie den Agenten vor der Bearbeitung der Einstellungen."
|
||||
},
|
||||
"CONFIGURATION$ERROR_FETCH_MODELS": {
|
||||
"en": "Failed to fetch models and agents",
|
||||
"zh-CN": "获取模型和智能体失败",
|
||||
"de": "Fehler beim Abrufen der Modelle und Agenten"
|
||||
},
|
||||
"SESSION$SERVER_CONNECTED_MESSAGE": {
|
||||
"en": "Connected to server",
|
||||
"zh-CN": "已连接到服务器",
|
||||
"de": "Verbindung zum Server hergestellt"
|
||||
},
|
||||
"SESSION$SESSION_HANDLING_ERROR_MESSAGE": {
|
||||
"en": "Error handling message",
|
||||
"zh-CN": "处理消息时发生错误",
|
||||
"de": "Fehler beim Verarbeiten der Nachricht"
|
||||
},
|
||||
"SESSION$SESSION_CONNECTION_ERROR_MESSAGE": {
|
||||
"en": "Error connecting to session",
|
||||
"zh-CN": "连接到会话时发生错误",
|
||||
"de": "Verbindung zur Sitzung fehlgeschlagen"
|
||||
},
|
||||
"SESSION$SOCKET_NOT_INITIALIZED_ERROR_MESSAGE": {
|
||||
"en": "Socket not initialized",
|
||||
"zh-CN": "Socket 未初始化",
|
||||
"de": "Socket nicht initialisiert"
|
||||
},
|
||||
"EXPLORER$UPLOAD_ERROR_MESSAGE": {
|
||||
"en": "Error uploading file",
|
||||
"zh-CN": "上传文件时发生错误",
|
||||
"de": "Fehler beim Hochladen der Datei"
|
||||
},
|
||||
"EXPLORER$LABEL_DROP_FILES": {
|
||||
"en": "Drop files here",
|
||||
"zh-CN": "将文件拖到这里",
|
||||
"de": "Dateien hier ablegen"
|
||||
},
|
||||
"EXPLORER$LABEL_WORKSPACE": {
|
||||
"en": "Workspace",
|
||||
"zh-CN": "工作区",
|
||||
"de": "Arbeitsbereich"
|
||||
},
|
||||
"LOAD_SESSION$MODAL_TITLE": {
|
||||
"en": "Return to existing session?",
|
||||
"de": "Zurück zu vorhandener Sitzung?",
|
||||
@@ -394,6 +434,26 @@
|
||||
"ar": "إرسال",
|
||||
"fr": "Envoyer"
|
||||
},
|
||||
"CHAT_INTERFACE$CHAT_MESSAGE_COPIED": {
|
||||
"en": "Message copied to clipboard",
|
||||
"zh-CN": "消息已复制到剪贴板",
|
||||
"de": "Nachricht in die Zwischenablage kopiert"
|
||||
},
|
||||
"CHAT_INTERFACE$CHAT_MESSAGE_COPY_FAILED": {
|
||||
"en": "Failed to copy message to clipboard",
|
||||
"zh-CN": "复制消息到剪贴板失败",
|
||||
"de": "Nachricht konnte nicht in die Zwischenablage kopiert werden"
|
||||
},
|
||||
"CHAT_INTERFACE$TOOLTIP_COPY_MESSAGE": {
|
||||
"en": "Copy message",
|
||||
"zh-CN": "复制消息",
|
||||
"de": "Nachricht kopieren"
|
||||
},
|
||||
"CHAT_INTERFACE$TOOLTIP_SEND_MESSAGE": {
|
||||
"en": "Send message",
|
||||
"zh-CN": "发送消息",
|
||||
"de": "Nachricht senden"
|
||||
},
|
||||
"CHAT_INTERFACE$INITIAL_MESSAGE": {
|
||||
"en": "Hi! I'm OpenDevin, an AI Software Engineer. What would you like to build with me today?",
|
||||
"zh-CN": "你好!我是 OpenDevin,一名 AI 软件工程师。今天想和我一起编写什么程序呢?",
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import i18next from "i18next";
|
||||
import toast from "#/utils/toast";
|
||||
import { handleAssistantMessage } from "./actions";
|
||||
import { getToken, setToken, clearToken } from "./auth";
|
||||
import ActionType from "#/types/ActionType";
|
||||
import { getSettings } from "./settings";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
const translate = (key: I18nKey) => i18next.t(key);
|
||||
|
||||
class Session {
|
||||
private static _socket: WebSocket | null = null;
|
||||
@@ -66,10 +70,12 @@ class Session {
|
||||
|
||||
private static _setupSocket(): void {
|
||||
if (!Session._socket) {
|
||||
throw new Error("Socket is not initialized.");
|
||||
throw new Error(
|
||||
translate(I18nKey.SESSION$SOCKET_NOT_INITIALIZED_ERROR_MESSAGE),
|
||||
);
|
||||
}
|
||||
Session._socket.onopen = (e) => {
|
||||
toast.success("ws", "Connected to server.");
|
||||
toast.success("ws", translate(I18nKey.SESSION$SERVER_CONNECTED_MESSAGE));
|
||||
Session._connecting = false;
|
||||
Session._initializeAgent();
|
||||
Session.callbacks.open?.forEach((callback) => {
|
||||
@@ -84,7 +90,10 @@ class Session {
|
||||
Session._history.push(data);
|
||||
} catch (err) {
|
||||
// TODO: report the error
|
||||
toast.error("ws", "Error handing message.");
|
||||
toast.error(
|
||||
"ws",
|
||||
translate(I18nKey.SESSION$SESSION_HANDLING_ERROR_MESSAGE),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (data.error && data.error_code === 401) {
|
||||
@@ -101,8 +110,10 @@ class Session {
|
||||
};
|
||||
|
||||
Session._socket.onerror = () => {
|
||||
const msg = "Connection failed. Retry...";
|
||||
toast.error("ws", msg);
|
||||
toast.error(
|
||||
"ws",
|
||||
translate(I18nKey.SESSION$SESSION_CONNECTION_ERROR_MESSAGE),
|
||||
);
|
||||
};
|
||||
|
||||
Session._socket.onclose = () => {
|
||||
@@ -135,15 +146,19 @@ class Session {
|
||||
return;
|
||||
}
|
||||
if (!Session.isConnected()) {
|
||||
throw new Error("Not connected to server.");
|
||||
throw new Error(
|
||||
translate(I18nKey.SESSION$SESSION_CONNECTION_ERROR_MESSAGE),
|
||||
);
|
||||
}
|
||||
|
||||
if (Session.isConnected()) {
|
||||
Session._socket?.send(message);
|
||||
Session._history.push(JSON.parse(message));
|
||||
} else {
|
||||
const msg = "Connection failed. Retry...";
|
||||
toast.error("ws", msg);
|
||||
toast.error(
|
||||
"ws",
|
||||
translate(I18nKey.SESSION$SESSION_CONNECTION_ERROR_MESSAGE),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user