From dfd9e57243bc5b3d6a2ddf3a36bae174a7a205bd Mon Sep 17 00:00:00 2001 From: neubig <398875+neubig@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:07:33 -0400 Subject: [PATCH 1/2] Localize canvas text labels --- .../automations/delete-confirmation-modal.tsx | 2 +- .../features/backends/backend-form-modal.tsx | 4 +- .../features/chat/chat-interface.tsx | 7 +- .../features/chat/chat-messages-skeleton.tsx | 6 +- .../chat/components/chat-input-actions.tsx | 2 +- src/components/features/chat/plan-preview.tsx | 10 +- .../conversation-tabs/conversation-tabs.tsx | 7 +- .../features/diff-viewer/loading-spinner.tsx | 6 +- .../git-branch-dropdown.tsx | 4 +- .../features/home/shared/clear-button.tsx | 6 +- .../features/home/shared/toggle-button.tsx | 6 +- .../folder-browser-modal.tsx | 2 +- .../features/settings/mobile-header.tsx | 2 +- .../settings/secrets-settings/secret-form.tsx | 4 +- .../features/sidebar/sidebar-rail-body.tsx | 4 +- .../features/skills/skills-toolbar.tsx | 2 +- src/hooks/chat/use-chat-attachment-upload.ts | 25 +- src/hooks/use-handle-ws-events.ts | 7 +- src/i18n/translation.json | 289 ++++++++++++++++++ src/ui/dropdown/clear-button.tsx | 6 +- src/utils/file-validation.ts | 31 +- src/utils/send-message-with-attachments.ts | 7 +- 22 files changed, 402 insertions(+), 37 deletions(-) diff --git a/src/components/features/automations/delete-confirmation-modal.tsx b/src/components/features/automations/delete-confirmation-modal.tsx index 6d2ea1d10..eb642ea2b 100644 --- a/src/components/features/automations/delete-confirmation-modal.tsx +++ b/src/components/features/automations/delete-confirmation-modal.tsx @@ -35,7 +35,7 @@ export function DeleteConfirmationModal({ type="button" onClick={onCancel} className="absolute right-4 top-4 text-muted hover:text-foreground" - aria-label="Close" + aria-label={t(I18nKey.BUTTON$CLOSE)} > diff --git a/src/components/features/backends/backend-form-modal.tsx b/src/components/features/backends/backend-form-modal.tsx index 35b1be14b..49709e4ea 100644 --- a/src/components/features/backends/backend-form-modal.tsx +++ b/src/components/features/backends/backend-form-modal.tsx @@ -446,7 +446,7 @@ export function BackendForm({ setConnectionError(null); }} onBlur={() => setNameTouched(true)} - placeholder="Production" + placeholder={t(I18nKey.BACKEND$NAME_PLACEHOLDER)} className="w-full" showRequiredTag error={nameError} @@ -622,7 +622,7 @@ function ManualConnectionColumn({ onClose }: { onClose: () => void }) { setName(value); setConnectionError(null); }} - placeholder="e.g. My Server" + placeholder={t(I18nKey.BACKEND$ADD_NAME_PLACEHOLDER)} className="w-full" />

diff --git a/src/components/features/chat/chat-interface.tsx b/src/components/features/chat/chat-interface.tsx index 131c089c0..b54886a52 100644 --- a/src/components/features/chat/chat-interface.tsx +++ b/src/components/features/chat/chat-interface.tsx @@ -33,7 +33,10 @@ import { useLlmConfigured } from "#/hooks/use-llm-configured"; import { Messages } from "#/components/conversation-events/chat/messages"; import { PendingUserMessages } from "./pending-user-messages"; import { useUnifiedUploadFiles } from "#/hooks/mutation/use-unified-upload-files"; -import { validateFiles } from "#/utils/file-validation"; +import { + formatFileValidationError, + validateFiles, +} from "#/utils/file-validation"; import { useConversationStore } from "#/stores/conversation-store"; import ConfirmationModeEnabled from "./confirmation-mode-enabled"; import { useTaskPolling } from "#/hooks/query/use-task-polling"; @@ -307,7 +310,7 @@ export function ChatInterface() { const validation = validateFiles(allFiles); if (!validation.isValid) { - displayErrorToast(`Error: ${validation.errorMessage}`); + displayErrorToast(formatFileValidationError(validation, t)); return; // Stop processing if validation fails } diff --git a/src/components/features/chat/chat-messages-skeleton.tsx b/src/components/features/chat/chat-messages-skeleton.tsx index 4012d68fc..dc23f8b43 100644 --- a/src/components/features/chat/chat-messages-skeleton.tsx +++ b/src/components/features/chat/chat-messages-skeleton.tsx @@ -1,5 +1,7 @@ import React from "react"; +import { useTranslation } from "react-i18next"; import { cn } from "#/utils/utils"; +import { I18nKey } from "#/i18n/declaration"; const SKELETON_PATTERN = [ { width: "w-[25%]", height: "h-4", align: "justify-end" }, @@ -22,11 +24,13 @@ function SkeletonBlock({ width, height }: { width: string; height: string }) { } export function ChatMessagesSkeleton() { + const { t } = useTranslation("openhands"); + return (

{SKELETON_PATTERN.map((item, i) => (
diff --git a/src/components/features/chat/components/chat-input-actions.tsx b/src/components/features/chat/components/chat-input-actions.tsx index 50392f603..1ebab199d 100644 --- a/src/components/features/chat/components/chat-input-actions.tsx +++ b/src/components/features/chat/components/chat-input-actions.tsx @@ -419,7 +419,7 @@ export function ChatInputActions({ ref={overflowTriggerRef} type="button" className={cn(chatInputIconButtonClassName, "size-6")} - aria-label="More input actions" + aria-label={t(I18nKey.CHAT_INTERFACE$MORE_INPUT_ACTIONS)} aria-expanded={isOverflowOpen} aria-haspopup="menu" onClick={(event) => { diff --git a/src/components/features/chat/plan-preview.tsx b/src/components/features/chat/plan-preview.tsx index 1dd63a7be..104c78c1b 100644 --- a/src/components/features/chat/plan-preview.tsx +++ b/src/components/features/chat/plan-preview.tsx @@ -15,6 +15,7 @@ import { import { useScrollContext } from "#/context/scroll-context"; const MAX_CONTENT_LENGTH = 300; +const BUILD_SHORTCUT_LABEL = String.fromCharCode(0x2318, 0x21a9); // Shine effect class for streaming text const SHINE_TEXT_CLASS = "shine-text"; @@ -31,7 +32,6 @@ interface PlanPreviewProps { isBuildDisabled?: boolean; } -/* eslint-disable i18next/no-literal-string */ export function PlanPreview({ planContent, isStreaming, @@ -128,11 +128,15 @@ export function PlanPreview({ : "hover:opacity-90 cursor-pointer", )} data-testid="plan-preview-build-button" + aria-label={t(I18nKey.COMMON$BUILD)} > {t(I18nKey.COMMON$BUILD)}{" "} - - ⌘↩ + diff --git a/src/components/features/conversation/conversation-tabs/conversation-tabs.tsx b/src/components/features/conversation/conversation-tabs/conversation-tabs.tsx index 2b030251b..6839acfde 100644 --- a/src/components/features/conversation/conversation-tabs/conversation-tabs.tsx +++ b/src/components/features/conversation/conversation-tabs/conversation-tabs.tsx @@ -23,6 +23,8 @@ import { AgentState } from "#/types/agent-state"; import { Typography } from "#/ui/typography"; import { mobileTopBarIconClassName } from "#/utils/mobile-top-bar-icon-button-classes"; +const BUILD_SHORTCUT_LABEL = String.fromCharCode(0x2318, 0x21a9); + export function ConversationTabs({ variant = "default", isPanelResizing = false, @@ -366,10 +368,11 @@ export function ConversationTabs({ : "cursor-pointer hover:opacity-90", )} data-testid="planner-tab-build-button" + aria-label={t(I18nKey.COMMON$BUILD)} > - {/* eslint-disable-next-line i18next/no-literal-string */} - {t(I18nKey.COMMON$BUILD)} ⌘↩ + {t(I18nKey.COMMON$BUILD)}{" "} +
diff --git a/src/components/features/diff-viewer/loading-spinner.tsx b/src/components/features/diff-viewer/loading-spinner.tsx index 4b251bce4..f20e170f2 100644 --- a/src/components/features/diff-viewer/loading-spinner.tsx +++ b/src/components/features/diff-viewer/loading-spinner.tsx @@ -1,10 +1,14 @@ +import { useTranslation } from "react-i18next"; import { cn } from "#/utils/utils"; +import { I18nKey } from "#/i18n/declaration"; export interface LoadingSpinnerProps { className?: string; } export function LoadingSpinner({ className }: LoadingSpinnerProps) { + const { t } = useTranslation("openhands"); + return (
); diff --git a/src/components/features/home/git-branch-dropdown/git-branch-dropdown.tsx b/src/components/features/home/git-branch-dropdown/git-branch-dropdown.tsx index 9af809bc1..ba86b0079 100644 --- a/src/components/features/home/git-branch-dropdown/git-branch-dropdown.tsx +++ b/src/components/features/home/git-branch-dropdown/git-branch-dropdown.tsx @@ -73,7 +73,9 @@ export function GitBranchDropdown({ selectedBranch, ); - const error = isError ? new Error("Failed to load branches") : null; + const error = isError + ? new Error(t(I18nKey.HOME$FAILED_TO_LOAD_BRANCHES)) + : null; // Handle clear const handleClear = useCallback(() => { diff --git a/src/components/features/home/shared/clear-button.tsx b/src/components/features/home/shared/clear-button.tsx index 56575925e..e74cdd779 100644 --- a/src/components/features/home/shared/clear-button.tsx +++ b/src/components/features/home/shared/clear-button.tsx @@ -1,4 +1,6 @@ import React from "react"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; import { cn } from "#/utils/utils"; interface ClearButtonProps { @@ -12,6 +14,8 @@ export function ClearButton({ onClear, testId = "dropdown-clear", }: ClearButtonProps) { + const { t } = useTranslation("openhands"); + return ( diff --git a/src/components/features/home/workspace-dropdown/folder-browser-modal.tsx b/src/components/features/home/workspace-dropdown/folder-browser-modal.tsx index 51368fa40..10f8af43f 100644 --- a/src/components/features/home/workspace-dropdown/folder-browser-modal.tsx +++ b/src/components/features/home/workspace-dropdown/folder-browser-modal.tsx @@ -254,7 +254,7 @@ export function FolderBrowserModal({ data-testid="folder-browser-up" onClick={() => parent && setCurrentPath(parent)} disabled={!parent} - aria-label="Up" + aria-label={t(I18nKey.COMMON$UP)} className="p-1 rounded hover:bg-[var(--oh-interactive-hover)] text-white disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer" > diff --git a/src/components/features/settings/mobile-header.tsx b/src/components/features/settings/mobile-header.tsx index d696d2344..856dbc578 100644 --- a/src/components/features/settings/mobile-header.tsx +++ b/src/components/features/settings/mobile-header.tsx @@ -24,7 +24,7 @@ export function MobileHeader({ type="button" onClick={onToggleMenu} className="p-2 rounded-md bg-tertiary hover:bg-tertiary transition-colors" - aria-label="Toggle settings menu" + aria-label={t(I18nKey.SETTINGS$TOGGLE_MENU)} > {error &&

{error}

} diff --git a/src/components/features/sidebar/sidebar-rail-body.tsx b/src/components/features/sidebar/sidebar-rail-body.tsx index c2f60b71e..e742486d9 100644 --- a/src/components/features/sidebar/sidebar-rail-body.tsx +++ b/src/components/features/sidebar/sidebar-rail-body.tsx @@ -159,7 +159,7 @@ export function SidebarRailBody({ onSearchChange("")} - aria-label="Clear search" + aria-label={t(I18nKey.MCP$SEARCH_CLEAR)} className="mr-2 p-1 rounded text-tertiary-alt hover:text-white cursor-pointer" > diff --git a/src/hooks/chat/use-chat-attachment-upload.ts b/src/hooks/chat/use-chat-attachment-upload.ts index 760b3a585..550844832 100644 --- a/src/hooks/chat/use-chat-attachment-upload.ts +++ b/src/hooks/chat/use-chat-attachment-upload.ts @@ -1,18 +1,24 @@ import { useCallback } from "react"; +import { useTranslation } from "react-i18next"; export type ChatAttachmentUploadOptions = { fromPaste?: boolean; }; import { isFileImage } from "#/utils/is-file-image"; import { displayErrorToast } from "#/utils/custom-toast-handlers"; -import { validateFiles } from "#/utils/file-validation"; +import { + formatFileValidationError, + validateFiles, +} from "#/utils/file-validation"; import { processFiles, processImages } from "#/utils/file-processing"; import { useConversationStore } from "#/stores/conversation-store"; +import { I18nKey } from "#/i18n/declaration"; /** * Shared attachment pipeline for home and conversation chat inputs. */ export function useChatAttachmentUpload() { + const { t } = useTranslation("openhands"); const { images, files, @@ -30,7 +36,7 @@ export function useChatAttachmentUpload() { const validation = validateFiles(selectedFiles, [...images, ...files]); if (!validation.isValid) { - displayErrorToast(`Error: ${validation.errorMessage}`); + displayErrorToast(formatFileValidationError(validation, t)); return; } @@ -67,27 +73,32 @@ export function useChatAttachmentUpload() { fileResults.failed.forEach(({ file, error }) => { removeFileLoading(file.name); displayErrorToast( - `Failed to process file ${file.name}: ${error.message}`, + t(I18nKey.CHAT_INTERFACE$FAILED_TO_PROCESS_FILE, { + name: file.name, + error: error.message, + }), ); }); imageResults.failed.forEach(({ file, error }) => { removeImageLoading(file.name); displayErrorToast( - `Failed to process image ${file.name}: ${error.message}`, + t(I18nKey.CHAT_INTERFACE$FAILED_TO_PROCESS_IMAGE, { + name: file.name, + error: error.message, + }), ); }); } catch { validFiles.forEach((file) => removeFileLoading(file.name)); validImages.forEach((image) => removeImageLoading(image.name)); - displayErrorToast( - "An unexpected error occurred while processing files", - ); + displayErrorToast(t(I18nKey.CHAT_INTERFACE$FILE_PROCESSING_UNEXPECTED)); } }, [ images, files, + t, addImages, addFiles, addFileLoading, diff --git a/src/hooks/use-handle-ws-events.ts b/src/hooks/use-handle-ws-events.ts index 5b3b78ac7..f2e967e1c 100644 --- a/src/hooks/use-handle-ws-events.ts +++ b/src/hooks/use-handle-ws-events.ts @@ -1,9 +1,11 @@ import React from "react"; +import { useTranslation } from "react-i18next"; import { generateAgentStateChangeEvent } from "#/services/agent-state-service"; import { AgentState } from "#/types/agent-state"; import { displayErrorToast } from "#/utils/custom-toast-handlers"; import { useEventStore } from "#/stores/use-event-store"; import { useSendMessage } from "#/hooks/use-send-message"; +import { I18nKey } from "#/i18n/declaration"; import { isAgentErrorEvent, isAgentServerEvent, @@ -23,6 +25,7 @@ const isTypedErrorEvent = ( "type" in event && event.type === "error"; export const useHandleWSEvents = () => { + const { t } = useTranslation("openhands"); const { send } = useSendMessage(); const events = useEventStore((state) => state.events); @@ -40,7 +43,7 @@ export const useHandleWSEvents = () => { if (isServerError(event)) { if (event.error_code === 401) { - displayErrorToast("Session expired."); + displayErrorToast(t(I18nKey.SESSION$EXPIRED)); return; } @@ -59,5 +62,5 @@ export const useHandleWSEvents = () => { send(generateAgentStateChangeEvent(AgentState.PAUSED)); } } - }, [events.length]); + }, [events.length, t]); }; diff --git a/src/i18n/translation.json b/src/i18n/translation.json index bae71cfe8..71a9baa93 100644 --- a/src/i18n/translation.json +++ b/src/i18n/translation.json @@ -8346,6 +8346,23 @@ "uk": "Час сеансу минув!", "ca": "La sessió ha caducat!" }, + "SESSION$EXPIRED": { + "en": "Session expired.", + "zh-CN": "会话已过期。", + "de": "Sitzung abgelaufen.", + "zh-TW": "會話已過期。", + "es": "La sesión ha caducado.", + "fr": "La session a expiré.", + "it": "Sessione scaduta.", + "pt": "Sessão expirada.", + "ko-KR": "세션이 만료되었습니다.", + "ar": "انتهت صلاحية الجلسة.", + "tr": "Oturum süresi doldu.", + "no": "Økten er utløpt.", + "ja": "セッションの有効期限が切れました。", + "uk": "Сеанс завершився.", + "ca": "La sessió ha caducat." + }, "EXPLORER$UPLOAD_ERROR_MESSAGE": { "en": "Error uploading file", "zh-CN": "上传时出错", @@ -10777,6 +10794,108 @@ "tr": "dosya olarak yükleme", "uk": "не завантажувати як файл" }, + "CHAT_INTERFACE$FILES_EXCEED_SIZE": { + "en": "Files exceeding {{limit}}MB are not allowed: {{files}}", + "ja": "{{limit}}MB を超えるファイルは許可されていません: {{files}}", + "zh-CN": "不允许超过 {{limit}}MB 的文件:{{files}}", + "zh-TW": "不允許超過 {{limit}}MB 的檔案:{{files}}", + "ko-KR": "{{limit}}MB를 초과하는 파일은 허용되지 않습니다: {{files}}", + "no": "Filer over {{limit}} MB er ikke tillatt: {{files}}", + "ar": "الملفات التي تتجاوز {{limit}} ميغابايت غير مسموح بها: {{files}}", + "de": "Dateien über {{limit}} MB sind nicht erlaubt: {{files}}", + "fr": "Les fichiers de plus de {{limit}} Mo ne sont pas autorisés : {{files}}", + "it": "I file oltre {{limit}} MB non sono consentiti: {{files}}", + "pt": "Arquivos acima de {{limit}} MB não são permitidos: {{files}}", + "es": "No se permiten archivos de más de {{limit}} MB: {{files}}", + "ca": "No es permeten fitxers de més de {{limit}} MB: {{files}}", + "tr": "{{limit}} MB üzerindeki dosyalara izin verilmez: {{files}}", + "uk": "Файли понад {{limit}} МБ не дозволені: {{files}}" + }, + "CHAT_INTERFACE$TOTAL_FILE_SIZE_EXCEEDS_LIMIT": { + "en": "Total file size would be {{size}}MB, exceeding the {{limit}}MB limit. Please select fewer or smaller files.", + "ja": "ファイルの合計サイズは {{size}}MB になり、{{limit}}MB の制限を超えます。より少ない、または小さいファイルを選択してください。", + "zh-CN": "总文件大小将为 {{size}}MB,超过 {{limit}}MB 限制。请选择更少或更小的文件。", + "zh-TW": "檔案總大小將為 {{size}}MB,超過 {{limit}}MB 限制。請選擇較少或較小的檔案。", + "ko-KR": "총 파일 크기가 {{size}}MB가 되어 {{limit}}MB 제한을 초과합니다. 더 적거나 더 작은 파일을 선택하세요.", + "no": "Total filstørrelse vil bli {{size}} MB, som overstiger grensen på {{limit}} MB. Velg færre eller mindre filer.", + "ar": "سيكون إجمالي حجم الملفات {{size}} ميغابايت، متجاوزًا حد {{limit}} ميغابايت. يُرجى اختيار ملفات أقل أو أصغر.", + "de": "Die Gesamtgröße der Dateien wäre {{size}} MB und überschreitet das Limit von {{limit}} MB. Bitte wählen Sie weniger oder kleinere Dateien aus.", + "fr": "La taille totale des fichiers serait de {{size}} Mo, ce qui dépasse la limite de {{limit}} Mo. Sélectionnez moins de fichiers ou des fichiers plus petits.", + "it": "La dimensione totale dei file sarebbe {{size}} MB, superando il limite di {{limit}} MB. Seleziona meno file o file più piccoli.", + "pt": "O tamanho total dos arquivos seria {{size}} MB, excedendo o limite de {{limit}} MB. Selecione menos arquivos ou arquivos menores.", + "es": "El tamaño total de los archivos sería de {{size}} MB, lo que supera el límite de {{limit}} MB. Seleccione menos archivos o archivos más pequeños.", + "ca": "La mida total dels fitxers seria de {{size}} MB, superant el límit de {{limit}} MB. Seleccioneu menys fitxers o fitxers més petits.", + "tr": "Toplam dosya boyutu {{size}} MB olur ve {{limit}} MB sınırını aşar. Lütfen daha az veya daha küçük dosyalar seçin.", + "uk": "Загальний розмір файлів становитиме {{size}} МБ, що перевищує ліміт {{limit}} МБ. Виберіть менше або менші файли." + }, + "CHAT_INTERFACE$INVALID_ATTACHMENTS": { + "en": "Invalid attachments", + "ja": "無効な添付ファイル", + "zh-CN": "附件无效", + "zh-TW": "附件無效", + "ko-KR": "잘못된 첨부 파일", + "no": "Ugyldige vedlegg", + "ar": "مرفقات غير صالحة", + "de": "Ungültige Anhänge", + "fr": "Pièces jointes non valides", + "it": "Allegati non validi", + "pt": "Anexos inválidos", + "es": "Adjuntos no válidos", + "ca": "Adjunts no vàlids", + "tr": "Geçersiz ekler", + "uk": "Недійсні вкладення" + }, + "CHAT_INTERFACE$FAILED_TO_PROCESS_FILE": { + "en": "Failed to process file {{name}}: {{error}}", + "ja": "ファイル {{name}} の処理に失敗しました: {{error}}", + "zh-CN": "处理文件 {{name}} 失败:{{error}}", + "zh-TW": "處理檔案 {{name}} 失敗:{{error}}", + "ko-KR": "파일 {{name}} 처리 실패: {{error}}", + "no": "Kunne ikke behandle filen {{name}}: {{error}}", + "ar": "فشلت معالجة الملف {{name}}: {{error}}", + "de": "Datei {{name}} konnte nicht verarbeitet werden: {{error}}", + "fr": "Échec du traitement du fichier {{name}} : {{error}}", + "it": "Impossibile elaborare il file {{name}}: {{error}}", + "pt": "Falha ao processar o arquivo {{name}}: {{error}}", + "es": "Error al procesar el archivo {{name}}: {{error}}", + "ca": "No s'ha pogut processar el fitxer {{name}}: {{error}}", + "tr": "{{name}} dosyası işlenemedi: {{error}}", + "uk": "Не вдалося обробити файл {{name}}: {{error}}" + }, + "CHAT_INTERFACE$FAILED_TO_PROCESS_IMAGE": { + "en": "Failed to process image {{name}}: {{error}}", + "ja": "画像 {{name}} の処理に失敗しました: {{error}}", + "zh-CN": "处理图片 {{name}} 失败:{{error}}", + "zh-TW": "處理圖片 {{name}} 失敗:{{error}}", + "ko-KR": "이미지 {{name}} 처리 실패: {{error}}", + "no": "Kunne ikke behandle bildet {{name}}: {{error}}", + "ar": "فشلت معالجة الصورة {{name}}: {{error}}", + "de": "Bild {{name}} konnte nicht verarbeitet werden: {{error}}", + "fr": "Échec du traitement de l'image {{name}} : {{error}}", + "it": "Impossibile elaborare l'immagine {{name}}: {{error}}", + "pt": "Falha ao processar a imagem {{name}}: {{error}}", + "es": "Error al procesar la imagen {{name}}: {{error}}", + "ca": "No s'ha pogut processar la imatge {{name}}: {{error}}", + "tr": "{{name}} görseli işlenemedi: {{error}}", + "uk": "Не вдалося обробити зображення {{name}}: {{error}}" + }, + "CHAT_INTERFACE$FILE_PROCESSING_UNEXPECTED": { + "en": "An unexpected error occurred while processing files", + "ja": "ファイルの処理中に予期しないエラーが発生しました", + "zh-CN": "处理文件时发生意外错误", + "zh-TW": "處理檔案時發生非預期錯誤", + "ko-KR": "파일을 처리하는 동안 예기치 않은 오류가 발생했습니다", + "no": "Det oppstod en uventet feil under behandling av filer", + "ar": "حدث خطأ غير متوقع أثناء معالجة الملفات", + "de": "Beim Verarbeiten der Dateien ist ein unerwarteter Fehler aufgetreten", + "fr": "Une erreur inattendue s'est produite lors du traitement des fichiers", + "it": "Si è verificato un errore imprevisto durante l'elaborazione dei file", + "pt": "Ocorreu um erro inesperado ao processar os arquivos", + "es": "Se produjo un error inesperado al procesar los archivos", + "ca": "S'ha produït un error inesperat en processar els fitxers", + "tr": "Dosyalar işlenirken beklenmeyen bir hata oluştu", + "uk": "Під час обробки файлів сталася неочікувана помилка" + }, "CHAT_INTERFACE$TOOLTIP_UPLOAD_IMAGE": { "en": "Upload image", "zh-CN": "上传图片", @@ -10896,6 +11015,40 @@ "uk": "Вниз", "ca": "Ves al final" }, + "CHAT_INTERFACE$MORE_INPUT_ACTIONS": { + "en": "More input actions", + "zh-CN": "更多输入操作", + "de": "Weitere Eingabeaktionen", + "ko-KR": "추가 입력 작업", + "no": "Flere inndatahandlinger", + "zh-TW": "更多輸入操作", + "it": "Altre azioni di input", + "pt": "Mais ações de entrada", + "es": "Más acciones de entrada", + "ar": "مزيد من إجراءات الإدخال", + "fr": "Plus d'actions de saisie", + "tr": "Daha fazla giriş eylemi", + "ja": "その他の入力アクション", + "uk": "Більше дій введення", + "ca": "Més accions d'entrada" + }, + "CHAT_INTERFACE$LOADING_CONVERSATION": { + "en": "Loading conversation", + "zh-CN": "正在加载对话", + "de": "Unterhaltung wird geladen", + "ko-KR": "대화 불러오는 중", + "no": "Laster samtale", + "zh-TW": "正在載入對話", + "it": "Caricamento conversazione", + "pt": "Carregando conversa", + "es": "Cargando conversación", + "ar": "جارٍ تحميل المحادثة", + "fr": "Chargement de la conversation", + "tr": "Konuşma yükleniyor", + "ja": "会話を読み込み中", + "uk": "Завантаження розмови", + "ca": "S'està carregant la conversa" + }, "CHAT_INTERFACE$MESSAGE_ARIA_LABEL": { "en": "Message from {{sender}}", "zh-CN": "来自 {{sender}} 的消息", @@ -11542,6 +11695,23 @@ "tr": "İsim", "ca": "Nom" }, + "SETTINGS$TOGGLE_MENU": { + "en": "Toggle settings menu", + "uk": "Перемкнути меню налаштувань", + "ja": "設定メニューを切り替え", + "zh-CN": "切换设置菜单", + "zh-TW": "切換設定選單", + "ko-KR": "설정 메뉴 전환", + "no": "Veksle innstillingsmeny", + "ar": "تبديل قائمة الإعدادات", + "de": "Einstellungsmenü umschalten", + "fr": "Afficher/masquer le menu des paramètres", + "it": "Attiva/disattiva menu impostazioni", + "pt": "Alternar menu de configurações", + "es": "Alternar menú de configuración", + "tr": "Ayarlar menüsünü aç/kapat", + "ca": "Commuta el menú de configuració" + }, "SECRETS$DESCRIPTION": { "en": "Description", "uk": "Опис", @@ -14619,6 +14789,23 @@ "tr": "Yeni Sohbet", "uk": "Нова розмова" }, + "SIDEBAR$NEW_CHAT": { + "en": "New Chat", + "ja": "新しいチャット", + "zh-CN": "新建聊天", + "zh-TW": "新建聊天", + "ko-KR": "새 채팅", + "no": "Ny chat", + "ar": "دردشة جديدة", + "de": "Neuer Chat", + "fr": "Nouveau chat", + "it": "Nuova chat", + "pt": "Novo chat", + "es": "Nuevo chat", + "ca": "Xat nou", + "tr": "Yeni Sohbet", + "uk": "Новий чат" + }, "STATUS$CONNECTING_TO_RUNTIME": { "en": "Connecting to runtime...", "zh-CN": "正在连接到运行时...", @@ -20212,6 +20399,23 @@ "uk": "наприклад OpenAI_API_Key", "ca": "p. ex. OpenAI_API_Key" }, + "SECRETS$NAME_PATTERN_HELP": { + "en": "Must start with a letter, contain only letters/numbers/underscores, and be 1-64 characters", + "ja": "文字で始まり、文字、数字、アンダースコアのみを含み、1〜64文字である必要があります", + "zh-CN": "必须以字母开头,只能包含字母、数字和下划线,长度为 1-64 个字符", + "zh-TW": "必須以字母開頭,只能包含字母、數字和底線,長度為 1-64 個字元", + "ko-KR": "문자로 시작해야 하며 문자, 숫자, 밑줄만 포함하고 1-64자여야 합니다", + "no": "Må starte med en bokstav, bare inneholde bokstaver/tall/understreker og være 1-64 tegn", + "it": "Deve iniziare con una lettera, contenere solo lettere/numeri/trattini bassi ed essere di 1-64 caratteri", + "pt": "Deve começar com uma letra, conter apenas letras/números/sublinhados e ter 1-64 caracteres", + "es": "Debe comenzar con una letra, contener solo letras/números/guiones bajos y tener 1-64 caracteres", + "ar": "يجب أن يبدأ بحرف، ويحتوي فقط على أحرف/أرقام/شرطات سفلية، وأن يكون من 1 إلى 64 حرفًا", + "fr": "Doit commencer par une lettre, contenir uniquement lettres/chiffres/tirets bas, et faire 1 à 64 caractères", + "tr": "Bir harfle başlamalı, yalnızca harf/rakam/alt çizgi içermeli ve 1-64 karakter olmalıdır", + "de": "Muss mit einem Buchstaben beginnen, nur Buchstaben/Zahlen/Unterstriche enthalten und 1-64 Zeichen lang sein", + "uk": "Має починатися з літери, містити лише літери/цифри/підкреслення та мати 1-64 символи", + "ca": "Ha de començar amb una lletra, contenir només lletres/números/guions baixos i tenir entre 1 i 64 caràcters" + }, "MODEL$CUSTOM_MODEL": { "en": "Custom Model", "ja": "カスタムモデル", @@ -23289,6 +23493,57 @@ "uk": "Переглянути більше", "ca": "Mostra més" }, + "COMMON$CLEAR_SELECTION": { + "en": "Clear selection", + "ja": "選択をクリア", + "zh-CN": "清除选择", + "zh-TW": "清除選取", + "ko-KR": "선택 지우기", + "no": "Fjern valg", + "it": "Cancella selezione", + "pt": "Limpar seleção", + "es": "Borrar selección", + "ar": "مسح التحديد", + "fr": "Effacer la sélection", + "tr": "Seçimi temizle", + "de": "Auswahl löschen", + "uk": "Очистити вибір", + "ca": "Esborra la selecció" + }, + "COMMON$TOGGLE_MENU": { + "en": "Toggle menu", + "ja": "メニューを切り替え", + "zh-CN": "切换菜单", + "zh-TW": "切換選單", + "ko-KR": "메뉴 전환", + "no": "Veksle meny", + "it": "Attiva/disattiva menu", + "pt": "Alternar menu", + "es": "Alternar menú", + "ar": "تبديل القائمة", + "fr": "Afficher/masquer le menu", + "tr": "Menüyü aç/kapat", + "de": "Menü umschalten", + "uk": "Перемкнути меню", + "ca": "Commuta el menú" + }, + "COMMON$UP": { + "en": "Up", + "ja": "上へ", + "zh-CN": "向上", + "zh-TW": "向上", + "ko-KR": "위로", + "no": "Opp", + "it": "Su", + "pt": "Para cima", + "es": "Arriba", + "ar": "أعلى", + "fr": "Monter", + "tr": "Yukarı", + "de": "Nach oben", + "uk": "Вгору", + "ca": "Amunt" + }, "COMMON$ARCHIVED": { "en": "Archived", "ja": "アーカイブ済み", @@ -26893,6 +27148,40 @@ "uk": "Ім'я хоста", "ca": "Nom del host" }, + "BACKEND$NAME_PLACEHOLDER": { + "en": "Production", + "ja": "本番", + "zh-CN": "生产环境", + "zh-TW": "正式環境", + "ko-KR": "프로덕션", + "no": "Produksjon", + "it": "Produzione", + "pt": "Produção", + "es": "Producción", + "ar": "الإنتاج", + "fr": "Production", + "tr": "Üretim", + "de": "Produktion", + "uk": "Продакшн", + "ca": "Producció" + }, + "BACKEND$ADD_NAME_PLACEHOLDER": { + "en": "e.g. My Server", + "ja": "例: My Server", + "zh-CN": "例如 My Server", + "zh-TW": "例如 My Server", + "ko-KR": "예: My Server", + "no": "f.eks. My Server", + "it": "es. My Server", + "pt": "ex. My Server", + "es": "ej. My Server", + "ar": "مثل My Server", + "fr": "ex. My Server", + "tr": "örn. My Server", + "de": "z.B. My Server", + "uk": "наприклад My Server", + "ca": "p. ex. My Server" + }, "BACKEND$HOST_LABEL": { "en": "Host", "ja": "ホスト", diff --git a/src/ui/dropdown/clear-button.tsx b/src/ui/dropdown/clear-button.tsx index 98b62c05a..466057aa2 100644 --- a/src/ui/dropdown/clear-button.tsx +++ b/src/ui/dropdown/clear-button.tsx @@ -1,16 +1,20 @@ import { X } from "lucide-react"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; interface ClearButtonProps { onClear: () => void; } export function ClearButton({ onClear }: ClearButtonProps) { + const { t } = useTranslation("openhands"); + return (