Просмотр исходного кода

Use i18n Keys (2) (#4464)

Co-authored-by: adrianamorenogt <adrianamorenogutierrez@gmail.com>
Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com>
Daniel Cruz 1 год назад
Родитель
Сommit
bb362cd377

+ 11 - 1
frontend/src/components/form/custom-input.tsx

@@ -1,3 +1,6 @@
+import { useTranslation } from "react-i18next";
+import { I18nKey } from "#/i18n/declaration";
+
 interface CustomInputProps {
   name: string;
   label: string;
@@ -13,12 +16,19 @@ export function CustomInput({
   defaultValue,
   type = "text",
 }: CustomInputProps) {
+  const { t } = useTranslation();
+
   return (
     <label htmlFor={name} className="flex flex-col gap-2">
       <span className="text-[11px] leading-4 tracking-[0.5px] font-[500] text-[#A3A3A3]">
         {label}
         {required && <span className="text-[#FF4D4F]">*</span>}
-        {!required && <span className="text-[#A3A3A3]"> (optional)</span>}
+        {!required && (
+          <span className="text-[#A3A3A3]">
+            {" "}
+            {t(I18nKey.CUSTOM_INPUT$OPTIONAL_LABEL)}
+          </span>
+        )}
       </span>
       <input
         id={name}

+ 33 - 23
frontend/src/components/form/settings-form.tsx

@@ -5,16 +5,18 @@ import {
   Switch,
 } from "@nextui-org/react";
 import { useFetcher, useLocation, useNavigate } from "@remix-run/react";
+import { useTranslation } from "react-i18next";
 import clsx from "clsx";
 import React from "react";
-import { ModalBackdrop } from "#/components/modals/modal-backdrop";
+import { organizeModelsAndProviders } from "#/utils/organizeModelsAndProviders";
 import { ModelSelector } from "#/components/modals/settings/ModelSelector";
-import { clientAction } from "#/routes/settings";
 import { Settings } from "#/services/settings";
+import { ModalBackdrop } from "#/components/modals/modal-backdrop";
+import { clientAction } from "#/routes/settings";
 import { extractModelAndProvider } from "#/utils/extractModelAndProvider";
-import { organizeModelsAndProviders } from "#/utils/organizeModelsAndProviders";
 import ModalButton from "../buttons/ModalButton";
 import { DangerModal } from "../modals/confirmation-modals/danger-modal";
+import { I18nKey } from "#/i18n/declaration";
 
 interface SettingsFormProps {
   disabled?: boolean;
@@ -35,6 +37,7 @@ export function SettingsForm({
 }: SettingsFormProps) {
   const location = useLocation();
   const navigate = useNavigate();
+  const { t } = useTranslation();
 
   const fetcher = useFetcher<typeof clientAction>();
   const formRef = React.useRef<HTMLFormElement>(null);
@@ -161,7 +164,7 @@ export function SettingsForm({
               label: "text-[#A3A3A3] text-xs",
             }}
           >
-            Advanced Options
+            {t(I18nKey.SETTINGS_FORM$ADVANCED_OPTIONS_LABEL)}
           </Switch>
 
           {showAdvancedOptions && (
@@ -171,7 +174,7 @@ export function SettingsForm({
                   htmlFor="custom-model"
                   className="font-[500] text-[#A3A3A3] text-xs"
                 >
-                  Custom Model
+                  {t(I18nKey.SETTINGS_FORM$CUSTOM_MODEL_LABEL)}
                 </label>
                 <Input
                   isDisabled={disabled}
@@ -190,7 +193,7 @@ export function SettingsForm({
                   htmlFor="base-url"
                   className="font-[500] text-[#A3A3A3] text-xs"
                 >
-                  Base URL
+                  {t(I18nKey.SETTINGS_FORM$BASE_URL_LABEL)}
                 </label>
                 <Input
                   isDisabled={disabled}
@@ -220,7 +223,7 @@ export function SettingsForm({
               htmlFor="api-key"
               className="font-[500] text-[#A3A3A3] text-xs"
             >
-              API Key
+              {t(I18nKey.SETTINGS_FORM$API_KEY_LABEL)}
             </label>
             <Input
               isDisabled={disabled}
@@ -234,14 +237,14 @@ export function SettingsForm({
               }}
             />
             <p className="text-sm text-[#A3A3A3]">
-              Don&apos;t know your API key?{" "}
+              {t(I18nKey.SETTINGS_FORM$DONT_KNOW_API_KEY_LABEL)}{" "}
               <a
                 href="https://docs.all-hands.dev/modules/usage/llms"
                 rel="noreferrer noopener"
                 target="_blank"
                 className="underline underline-offset-2"
               >
-                Click here for instructions
+                {t(I18nKey.SETTINGS_FORM$CLICK_HERE_FOR_INSTRUCTIONS_LABEL)}
               </a>
             </p>
           </fieldset>
@@ -255,7 +258,7 @@ export function SettingsForm({
                 htmlFor="agent"
                 className="font-[500] text-[#A3A3A3] text-xs"
               >
-                Agent
+                {t(I18nKey.SETTINGS_FORM$AGENT_LABEL)}
               </label>
               <Autocomplete
                 isDisabled={disabled}
@@ -291,7 +294,7 @@ export function SettingsForm({
                   htmlFor="security-analyzer"
                   className="font-[500] text-[#A3A3A3] text-xs"
                 >
-                  Security Analyzer (Optional)
+                  {t(I18nKey.SETTINGS_FORM$SECURITY_ANALYZER_LABEL)}
                 </label>
                 <Autocomplete
                   isDisabled={disabled}
@@ -334,7 +337,7 @@ export function SettingsForm({
                   label: "text-[#A3A3A3] text-xs",
                 }}
               >
-                Enable Confirmation Mode
+                {t(I18nKey.SETTINGS_FORM$ENABLE_CONFIRMATION_MODE_LABEL)}
               </Switch>
             </>
           )}
@@ -345,18 +348,18 @@ export function SettingsForm({
             <ModalButton
               disabled={disabled || fetcher.state === "submitting"}
               type="submit"
-              text="Save"
+              text={t(I18nKey.SETTINGS_FORM$SAVE_LABEL)}
               className="bg-[#4465DB] w-full"
             />
             <ModalButton
-              text="Close"
+              text={t(I18nKey.SETTINGS_FORM$CLOSE_LABEL)}
               className="bg-[#737373] w-full"
               onClick={handleCloseClick}
             />
           </div>
           <ModalButton
             disabled={disabled}
-            text="Reset to defaults"
+            text={t(I18nKey.SETTINGS_FORM$RESET_TO_DEFAULTS_LABEL)}
             variant="text-like"
             className="text-danger self-start"
             onClick={() => {
@@ -369,15 +372,17 @@ export function SettingsForm({
       {confirmResetDefaultsModalOpen && (
         <ModalBackdrop>
           <DangerModal
-            title="Are you sure?"
-            description="All saved information in your AI settings will be deleted including any API keys."
+            title={t(I18nKey.SETTINGS_FORM$ARE_YOU_SURE_LABEL)}
+            description={t(
+              I18nKey.SETTINGS_FORM$ALL_INFORMATION_WILL_BE_DELETED_MESSAGE,
+            )}
             buttons={{
               danger: {
-                text: "Reset Defaults",
+                text: t(I18nKey.SETTINGS_FORM$RESET_TO_DEFAULTS_LABEL),
                 onClick: handleConfirmResetSettings,
               },
               cancel: {
-                text: "Cancel",
+                text: t(I18nKey.SETTINGS_FORM$CANCEL_LABEL),
                 onClick: () => setConfirmResetDefaultsModalOpen(false),
               },
             }}
@@ -387,12 +392,17 @@ export function SettingsForm({
       {confirmEndSessionModalOpen && (
         <ModalBackdrop>
           <DangerModal
-            title="End Session"
-            description="Changing your settings will clear your workspace and start a new session. Are you sure you want to continue?"
+            title={t(I18nKey.SETTINGS_FORM$END_SESSION_LABEL)}
+            description={t(
+              I18nKey.SETTINGS_FORM$CHANGING_WORKSPACE_WARNING_MESSAGE,
+            )}
             buttons={{
-              danger: { text: "End Session", onClick: handleConfirmEndSession },
+              danger: {
+                text: t(I18nKey.SETTINGS_FORM$END_SESSION_LABEL),
+                onClick: handleConfirmEndSession,
+              },
               cancel: {
-                text: "Cancel",
+                text: t(I18nKey.SETTINGS_FORM$CANCEL_LABEL),
                 onClick: () => setConfirmEndSessionModalOpen(false),
               },
             }}

+ 7 - 4
frontend/src/components/modals/AccountSettingsModal.tsx

@@ -1,5 +1,6 @@
 import { useFetcher, useRouteLoaderData } from "@remix-run/react";
 import React from "react";
+import { useTranslation } from "react-i18next";
 import { BaseModalTitle } from "./confirmation-modals/BaseModal";
 import ModalBody from "./ModalBody";
 import ModalButton from "../buttons/ModalButton";
@@ -9,6 +10,7 @@ import { clientLoader } from "#/routes/_oh";
 import { clientAction as settingsClientAction } from "#/routes/settings";
 import { clientAction as loginClientAction } from "#/routes/login";
 import { AvailableLanguages } from "#/i18n";
+import { I18nKey } from "#/i18n/declaration";
 
 interface AccountSettingsModalProps {
   onClose: () => void;
@@ -23,6 +25,7 @@ function AccountSettingsModal({
   gitHubError,
   analyticsConsent,
 }: AccountSettingsModalProps) {
+  const { t } = useTranslation();
   const data = useRouteLoaderData<typeof clientLoader>("routes/_oh");
   const settingsFetcher = useFetcher<typeof settingsClientAction>({
     key: "settings",
@@ -86,13 +89,13 @@ function AccountSettingsModal({
           />
           {gitHubError && (
             <p className="text-danger text-xs">
-              GitHub token is invalid. Please try again.
+              {t(I18nKey.ACCOUNT_SETTINGS_MODAL$GITHUB_TOKEN_INVALID)}
             </p>
           )}
           {data?.ghToken && !gitHubError && (
             <ModalButton
               variant="text-like"
-              text="Disconnect"
+              text={t(I18nKey.ACCOUNT_SETTINGS_MODAL$DISCONNECT)}
               onClick={() => {
                 settingsFetcher.submit(
                   {},
@@ -122,11 +125,11 @@ function AccountSettingsModal({
             }
             type="submit"
             intent="account"
-            text="Save"
+            text={t(I18nKey.ACCOUNT_SETTINGS_MODAL$SAVE)}
             className="bg-[#4465DB]"
           />
           <ModalButton
-            text="Close"
+            text={t(I18nKey.ACCOUNT_SETTINGS_MODAL$CLOSE)}
             onClick={onClose}
             className="bg-[#737373]"
           />

+ 11 - 4
frontend/src/components/modals/ConnectToGitHubByTokenModal.tsx

@@ -1,4 +1,5 @@
 import { Form, useNavigation } from "@remix-run/react";
+import { useTranslation } from "react-i18next";
 import {
   BaseModalDescription,
   BaseModalTitle,
@@ -7,10 +8,11 @@ import ModalButton from "../buttons/ModalButton";
 import AllHandsLogo from "#/assets/branding/all-hands-logo-spark.svg?react";
 import ModalBody from "./ModalBody";
 import { CustomInput } from "../form/custom-input";
+import { I18nKey } from "#/i18n/declaration";
 
 function ConnectToGitHubByTokenModal() {
   const navigation = useNavigation();
-
+  const { t } = useTranslation();
   return (
     <ModalBody testID="auth-modal">
       <div className="flex flex-col gap-2">
@@ -29,13 +31,18 @@ function ConnectToGitHubByTokenModal() {
             required
           />
           <p className="text-xs text-[#A3A3A3]">
-            By connecting you agree to our{" "}
-            <span className="text-hyperlink">terms of service</span>.
+            {t(
+              I18nKey.CONNECT_TO_GITHUB_BY_TOKEN_MODAL$BY_CONNECTING_YOU_AGREE,
+            )}{" "}
+            <span className="text-hyperlink">
+              {t(I18nKey.CONNECT_TO_GITHUB_BY_TOKEN_MODAL$TERMS_OF_SERVICE)}
+            </span>
+            .
           </p>
         </label>
         <ModalButton
           type="submit"
-          text="Continue"
+          text={t(I18nKey.CONNECT_TO_GITHUB_BY_TOKEN_MODAL$CONTINUE)}
           className="bg-[#791B80] w-full"
           disabled={navigation.state === "loading"}
         />

+ 5 - 1
frontend/src/components/modals/LoadingProject.tsx

@@ -1,6 +1,8 @@
+import { useTranslation } from "react-i18next";
 import LoadingSpinnerOuter from "#/assets/loading-outer.svg?react";
 import { cn } from "#/utils/utils";
 import ModalBody from "./ModalBody";
+import { I18nKey } from "#/i18n/declaration";
 
 interface LoadingSpinnerProps {
   size: "small" | "large";
@@ -28,10 +30,12 @@ interface LoadingProjectModalProps {
 }
 
 function LoadingProjectModal({ message }: LoadingProjectModalProps) {
+  const { t } = useTranslation();
+
   return (
     <ModalBody>
       <span className="text-xl leading-6 -tracking-[0.01em] font-semibold">
-        {message || "Loading..."}
+        {message || t(I18nKey.LOADING_PROJECT$LOADING)}
       </span>
       <LoadingSpinner size="large" />
     </ModalBody>

+ 7 - 4
frontend/src/components/modals/connect-to-github-modal.tsx

@@ -1,4 +1,5 @@
 import { useFetcher, useRouteLoaderData } from "@remix-run/react";
+import { useTranslation } from "react-i18next";
 import ModalBody from "./ModalBody";
 import { CustomInput } from "../form/custom-input";
 import ModalButton from "../buttons/ModalButton";
@@ -8,6 +9,7 @@ import {
 } from "./confirmation-modals/BaseModal";
 import { clientLoader } from "#/routes/_oh";
 import { clientAction } from "#/routes/login";
+import { I18nKey } from "#/i18n/declaration";
 
 interface ConnectToGitHubModalProps {
   onClose: () => void;
@@ -16,6 +18,7 @@ interface ConnectToGitHubModalProps {
 export function ConnectToGitHubModal({ onClose }: ConnectToGitHubModalProps) {
   const data = useRouteLoaderData<typeof clientLoader>("routes/_oh");
   const fetcher = useFetcher<typeof clientAction>({ key: "login" });
+  const { t } = useTranslation();
 
   return (
     <ModalBody>
@@ -24,14 +27,14 @@ export function ConnectToGitHubModal({ onClose }: ConnectToGitHubModalProps) {
         <BaseModalDescription
           description={
             <span>
-              Get your token{" "}
+              {t(I18nKey.CONNECT_TO_GITHUB_MODAL$GET_YOUR_TOKEN)}{" "}
               <a
                 href="https://github.com/settings/tokens/new?description=openhands-app&scopes=repo,user,workflow"
                 target="_blank"
                 rel="noreferrer noopener"
                 className="text-[#791B80] underline"
               >
-                here
+                {t(I18nKey.CONNECT_TO_GITHUB_MODAL$HERE)}
               </a>
             </span>
           }
@@ -55,13 +58,13 @@ export function ConnectToGitHubModal({ onClose }: ConnectToGitHubModalProps) {
           <ModalButton
             testId="connect-to-github"
             type="submit"
-            text="Connect"
+            text={t(I18nKey.CONNECT_TO_GITHUB_MODAL$CONNECT)}
             disabled={fetcher.state === "submitting"}
             className="bg-[#791B80] w-full"
           />
           <ModalButton
             onClick={onClose}
-            text="Close"
+            text={t(I18nKey.CONNECT_TO_GITHUB_MODAL$CLOSE)}
             className="bg-[#737373] w-full"
           />
         </div>

+ 5 - 1
frontend/src/components/modals/security/Security.tsx

@@ -1,6 +1,8 @@
 import React from "react";
+import { useTranslation } from "react-i18next";
 import SecurityInvariant from "./invariant/Invariant";
 import BaseModal from "../base-modal/BaseModal";
+import { I18nKey } from "#/i18n/declaration";
 
 interface SecurityProps {
   isOpen: boolean;
@@ -17,11 +19,13 @@ const SecurityAnalyzers: Record<SecurityAnalyzerOption, React.ElementType> = {
 };
 
 function Security({ isOpen, onOpenChange, securityAnalyzer }: SecurityProps) {
+  const { t } = useTranslation();
+
   const AnalyzerComponent =
     securityAnalyzer &&
     SecurityAnalyzers[securityAnalyzer as SecurityAnalyzerOption]
       ? SecurityAnalyzers[securityAnalyzer as SecurityAnalyzerOption]
-      : () => <div>Unknown security analyzer chosen</div>;
+      : () => <div>{t(I18nKey.SECURITY$UNKNOWN_ANALYZER_LABEL)}</div>;
 
   return (
     <BaseModal

+ 19 - 18
frontend/src/components/modals/security/invariant/Invariant.tsx

@@ -123,7 +123,7 @@ function SecurityInvariant(): JSX.Element {
 
   async function exportTraces(): Promise<void> {
     const data = await request(`/api/security/export-trace`);
-    toast.info("Trace exported");
+    toast.info(t(I18nKey.INVARIANT$TRACE_EXPORTED_MESSAGE));
 
     const filename = `openhands-trace-${getFormattedDateTime()}.json`;
     downloadJSON(data, filename);
@@ -134,7 +134,7 @@ function SecurityInvariant(): JSX.Element {
       method: "POST",
       body: JSON.stringify({ policy }),
     });
-    toast.info("Policy updated");
+    toast.info(t(I18nKey.INVARIANT$POLICY_UPDATED_MESSAGE));
   }
 
   async function updateSettings(): Promise<void> {
@@ -143,7 +143,7 @@ function SecurityInvariant(): JSX.Element {
       method: "POST",
       body: JSON.stringify(payload),
     });
-    toast.info("Settings updated");
+    toast.info(t(I18nKey.INVARIANT$SETTINGS_UPDATED_MESSAGE));
   }
 
   const handleExportTraces = useCallback(() => {
@@ -162,9 +162,9 @@ function SecurityInvariant(): JSX.Element {
     logs: (
       <>
         <div className="flex justify-between items-center border-b border-neutral-600 mb-4 p-4">
-          <h2 className="text-2xl">Logs</h2>
+          <h2 className="text-2xl">{t(I18nKey.INVARIANT$LOG_LABEL)}</h2>
           <Button onClick={handleExportTraces} className="bg-neutral-700">
-            Export Trace
+            {t(I18nKey.INVARIANT$EXPORT_TRACE_LABEL)}
           </Button>
         </div>
         <div className="flex-1 p-4 max-h-screen overflow-y-auto" ref={logsRef}>
@@ -195,9 +195,9 @@ function SecurityInvariant(): JSX.Element {
     policy: (
       <>
         <div className="flex justify-between items-center border-b border-neutral-600 mb-4 p-4">
-          <h2 className="text-2xl">Policy</h2>
+          <h2 className="text-2xl">{t(I18nKey.INVARIANT$POLICY_LABEL)}</h2>
           <Button className="bg-neutral-700" onClick={handleUpdatePolicy}>
-            Update Policy
+            {t(I18nKey.INVARIANT$UPDATE_POLICY_LABEL)}
           </Button>
         </div>
         <div className="flex grow items-center justify-center">
@@ -214,14 +214,16 @@ function SecurityInvariant(): JSX.Element {
     settings: (
       <>
         <div className="flex justify-between items-center border-b border-neutral-600 mb-4 p-4">
-          <h2 className="text-2xl">Settings</h2>
+          <h2 className="text-2xl">{t(I18nKey.INVARIANT$SETTINGS_LABEL)}</h2>
           <Button className="bg-neutral-700" onClick={handleUpdateSettings}>
-            Update Settings
+            {t(I18nKey.INVARIANT$UPDATE_SETTINGS_LABEL)}
           </Button>
         </div>
         <div className="flex grow p-4">
           <div className="flex flex-col w-full">
-            <p className="mb-2">Ask for user confirmation on risk severity:</p>
+            <p className="mb-2">
+              {t(I18nKey.INVARIANT$ASK_CONFIRMATION_RISK_SEVERITY_LABEL)}
+            </p>
             <Select
               placeholder="Select risk severity"
               value={selectedRisk}
@@ -264,7 +266,7 @@ function SecurityInvariant(): JSX.Element {
                 key={ActionSecurityRisk.HIGH + 1}
                 aria-label="Don't ask for confirmation"
               >
-                Don&apos;t ask for confirmation
+                {t(I18nKey.INVARIANT$DONT_ASK_FOR_CONFIRMATION_LABEL)}
               </SelectItem>
             </Select>
           </div>
@@ -278,18 +280,17 @@ function SecurityInvariant(): JSX.Element {
       <div className="w-60 bg-neutral-800 border-r border-r-neutral-600 p-4 flex-shrink-0">
         <div className="text-center mb-2">
           <InvariantLogoIcon className="mx-auto mb-1" />
-          <b>Invariant Analyzer</b>
+          <b>{t(I18nKey.INVARIANT$INVARIANT_ANALYZER_LABEL)}</b>
         </div>
         <p className="text-[0.6rem]">
-          Invariant Analyzer continuously monitors your OpenHands agent for
-          security issues.{" "}
+          {t(I18nKey.INVARIANT$INVARIANT_ANALYZER_MESSAGE)}{" "}
           <a
             className="underline"
             href="https://github.com/invariantlabs-ai/invariant"
             target="_blank"
             rel="noreferrer"
           >
-            Click to learn more
+            {t(I18nKey.INVARIANT$CLICK_TO_LEARN_MORE_LABEL)}
           </a>
         </p>
         <hr className="border-t border-neutral-600 my-2" />
@@ -298,19 +299,19 @@ function SecurityInvariant(): JSX.Element {
             className={`cursor-pointer p-2 rounded ${activeSection === "logs" && "bg-neutral-600"}`}
             onClick={() => setActiveSection("logs")}
           >
-            Logs
+            {t(I18nKey.INVARIANT$LOG_LABEL)}
           </div>
           <div
             className={`cursor-pointer p-2 rounded ${activeSection === "policy" && "bg-neutral-600"}`}
             onClick={() => setActiveSection("policy")}
           >
-            Policy
+            {t(I18nKey.INVARIANT$POLICY_LABEL)}
           </div>
           <div
             className={`cursor-pointer p-2 rounded ${activeSection === "settings" && "bg-neutral-600"}`}
             onClick={() => setActiveSection("settings")}
           >
-            Settings
+            {t(I18nKey.INVARIANT$SETTINGS_LABEL)}
           </div>
         </ul>
       </div>

+ 7 - 1
frontend/src/components/project-menu/project-menu-details-placeholder.tsx

@@ -1,5 +1,7 @@
+import { useTranslation } from "react-i18next";
 import { cn } from "#/utils/utils";
 import CloudConnection from "#/assets/cloud-connection.svg?react";
+import { I18nKey } from "#/i18n/declaration";
 
 interface ProjectMenuDetailsPlaceholderProps {
   isConnectedToGitHub: boolean;
@@ -10,9 +12,13 @@ export function ProjectMenuDetailsPlaceholder({
   isConnectedToGitHub,
   onConnectToGitHub,
 }: ProjectMenuDetailsPlaceholderProps) {
+  const { t } = useTranslation();
+
   return (
     <div className="flex flex-col">
-      <span className="text-sm leading-6 font-semibold">New Project</span>
+      <span className="text-sm leading-6 font-semibold">
+        {t(I18nKey.PROJECT_MENU_DETAILS_PLACEHOLDER$NEW_PROJECT_LABEL)}
+      </span>
       <button
         type="button"
         onClick={onConnectToGitHub}

+ 5 - 1
frontend/src/components/project-menu/project-menu-details.tsx

@@ -1,5 +1,7 @@
+import { useTranslation } from "react-i18next";
 import ExternalLinkIcon from "#/assets/external-link.svg?react";
 import { formatTimeDelta } from "#/utils/format-time-delta";
+import { I18nKey } from "#/i18n/declaration";
 
 interface ProjectMenuDetailsProps {
   repoName: string;
@@ -12,6 +14,7 @@ export function ProjectMenuDetails({
   avatar,
   lastCommit,
 }: ProjectMenuDetailsProps) {
+  const { t } = useTranslation();
   return (
     <div className="flex flex-col">
       <a
@@ -32,7 +35,8 @@ export function ProjectMenuDetails({
       >
         <span>{lastCommit.sha.slice(-7)}</span> <span>&middot;</span>{" "}
         <span>
-          {formatTimeDelta(new Date(lastCommit.commit.author.date))} ago
+          {formatTimeDelta(new Date(lastCommit.commit.author.date))}{" "}
+          {t(I18nKey.PROJECT_MENU_DETAILS$AGO_LABEL)}
         </span>
       </a>
     </div>

+ 218 - 1
frontend/src/i18n/translation.json

@@ -798,7 +798,96 @@
     "tr": "İptal"
   },
   "FEEDBACK$EMAIL_PLACEHOLDER": {
-    "en": "Enter your email address."
+    "en": "Enter your email address",
+    "es": "Ingresa tu correo electrónico"
+  },
+  "FEEDBACK$PASSWORD_COPIED_MESSAGE": {
+    "en": "Password copied to clipboard.",
+    "es": "Contraseña copiada al portapapeles."
+  },
+  "FEEDBACK$GO_TO_FEEDBACK": {
+    "en": "Go to shared feedback",
+    "es": "Ir a feedback compartido"
+  },
+  "FEEDBACK$PASSWORD": {
+    "en": "Password:",
+    "es": "Contraseña:"
+  },
+  "FEEDBACK$INVALID_EMAIL_FORMAT": {
+    "en": "Invalid email format",
+    "es": "Formato de correo inválido"
+  },
+  "FEEDBACK$FAILED_TO_SHARE": {
+    "en": "Failed to share, please contact the developers:",
+    "es": "Error al compartir, por favor contacta con los desarrolladores:"
+  },
+  "FEEDBACK$COPY_LABEL": {
+    "en": "Copy",
+    "es": "Copiar"
+  },
+  "FEEDBACK$SHARING_SETTINGS_LABEL": {
+    "en": "Sharing settings",
+    "es": "Configuración de compartir"
+  },
+  "SECURITY$UNKNOWN_ANALYZER_LABEL":{
+    "en": "Unknown security analyzer chosen",
+    "es": "Analizador de seguridad desconocido"
+  },
+  "INVARIANT$UPDATE_POLICY_LABEL": {
+    "en": "Update Policy",
+    "es": "Actualizar política"
+  },
+  "INVARIANT$UPDATE_SETTINGS_LABEL": {
+    "en": "Update Settings",
+    "es": "Actualizar configuración"
+  },
+  "INVARIANT$SETTINGS_LABEL": {
+    "en": "Settings",
+    "es": "Configuración"
+  },
+  "INVARIANT$ASK_CONFIRMATION_RISK_SEVERITY_LABEL": {
+    "en": "Ask for user confirmation on risk severity:",
+    "es": "Preguntar por confirmación del usuario sobre severidad del riesgo:"
+  },
+  "INVARIANT$DONT_ASK_FOR_CONFIRMATION_LABEL": {
+    "en": "Don't ask for confirmation",
+    "es": "No solicitar confirmación"
+  },
+  "INVARIANT$INVARIANT_ANALYZER_LABEL": {
+    "en": "Invariant Analyzer",
+    "es": "Analizador de invariantes"
+  },
+  "INVARIANT$INVARIANT_ANALYZER_MESSAGE": {
+    "en": "Invariant Analyzer continuously monitors your OpenHands agent for security issues.",
+    "es": "Analizador de invariantes continuamente monitorea tu agente de OpenHands por problemas de seguridad."
+  },
+  "INVARIANT$CLICK_TO_LEARN_MORE_LABEL": {
+    "en": "Click to learn more",
+    "es": "Clic para aprender más"
+  },
+  "INVARIANT$POLICY_LABEL": {
+    "en": "Policy",
+    "es": "Política"
+  },
+  "INVARIANT$LOG_LABEL": {
+    "en": "Logs",
+    "es": "Logs"
+  },
+  "INVARIANT$EXPORT_TRACE_LABEL": {
+    "en": "Export Trace",
+    "es": "Exportar traza"
+  },
+  "INVARIANT$TRACE_EXPORTED_MESSAGE": {
+    "en": "Trace exported",
+    "es": "Traza exportada"
+  },
+  "INVARIANT$POLICY_UPDATED_MESSAGE": {
+    "en": "Policy updated",
+    "es": "Política actualizada"
+  },
+  "INVARIANT$SETTINGS_UPDATED_MESSAGE": {
+    "en": "Settings updated",
+    "es": "Configuración actualizada"
   },
   "CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE": {
     "en": "Starting up!",
@@ -1517,6 +1606,134 @@
     "fr": "En attente que le client soit prêt...",
     "tr": "İstemcinin hazır olması bekleniyor..."
   },
+  "ACCOUNT_SETTINGS_MODAL$DISCONNECT":{
+    "en": "Disconnect",
+    "es": "Desconectar"
+  },
+  "ACCOUNT_SETTINGS_MODAL$SAVE":{
+    "en": "Save",
+    "es": "Guardar"
+  },
+  "ACCOUNT_SETTINGS_MODAL$CLOSE":{
+    "en": "Close",
+    "es": ""
+  },
+  "ACCOUNT_SETTINGS_MODAL$GITHUB_TOKEN_INVALID":{
+    "en": "GitHub token is invalid. Please try again.",
+    "es": ""
+  },
+  "CONNECT_TO_GITHUB_MODAL$GET_YOUR_TOKEN": {
+    "en": "Get your token",
+    "es": "Obten tu token"
+  },
+  "CONNECT_TO_GITHUB_MODAL$HERE": {
+    "en": "here",
+    "es": "aquí"
+  },
+  "CONNECT_TO_GITHUB_MODAL$CONNECT": {
+    "en": "Connect",
+    "es": "Conectar"
+  },
+  "CONNECT_TO_GITHUB_MODAL$CLOSE": {
+    "en": "Close",
+    "es": "Cerrar"
+  },
+  "CONNECT_TO_GITHUB_BY_TOKEN_MODAL$BY_CONNECTING_YOU_AGREE": {
+    "en": "By connecting you agree to our",
+    "es": "Al conectarte tu aceptas nuestros"
+  },
+  "CONNECT_TO_GITHUB_BY_TOKEN_MODAL$TERMS_OF_SERVICE": {
+    "en": "terms of service",
+    "es": "términos de servicio"
+  },
+  "CONNECT_TO_GITHUB_BY_TOKEN_MODAL$CONTINUE": {
+    "en": "Continue",
+    "es": "Continuar"
+  },
+  "LOADING_PROJECT$LOADING": {
+    "en": "Loading...",
+    "es": "Cargando..."
+  },
+  "CUSTOM_INPUT$OPTIONAL_LABEL": {
+    "en": "(Optional)",
+    "es": "(Opcional)"
+  },
+  "SETTINGS_FORM$ADVANCED_OPTIONS_LABEL": {
+    "en": "Advanced Options",
+    "es": "Opciones avanzadas"
+  },
+  "SETTINGS_FORM$CUSTOM_MODEL_LABEL": {
+    "en": "Custom Model",
+    "es": "Modelo personalizado"
+  },
+  "SETTINGS_FORM$BASE_URL_LABEL": {
+    "en": "Base URL",
+    "es": "URL base"
+  },
+  "SETTINGS_FORM$API_KEY_LABEL": {
+    "en": "API Key",
+    "es": "API Key"
+  },
+  "SETTINGS_FORM$DONT_KNOW_API_KEY_LABEL": {
+    "en": "Don't know your API key?",
+    "es": "¿No sabes tu API key?"
+  },
+  "SETTINGS_FORM$CLICK_HERE_FOR_INSTRUCTIONS_LABEL": {
+    "en": "Click here for instructions",
+    "es": "Clic aquí para instrucciones"
+  },
+  "SETTINGS_FORM$AGENT_LABEL": {
+    "en": "Agent",
+    "es": "Agente"
+  },
+  "SETTINGS_FORM$SECURITY_ANALYZER_LABEL": {
+    "en": "Security Analyzer (Optional)",
+    "es": "Analizador de seguridad (opcional)"
+  },
+  "SETTINGS_FORM$ENABLE_CONFIRMATION_MODE_LABEL": {
+    "en": "Enable Confirmation Mode",
+    "es": "Habilitar modo de confirmación"
+  },
+  "SETTINGS_FORM$SAVE_LABEL": {
+    "en": "Save",
+    "es": "Guardar"
+  },
+  "SETTINGS_FORM$CLOSE_LABEL": {
+    "en": "Close",
+    "es": "Cerrar"
+  },
+  "SETTINGS_FORM$RESET_TO_DEFAULTS_LABEL": {
+    "en": "Reset to defaults",
+    "es": "Reiniciar valores por defect"
+  },
+  "SETTINGS_FORM$CANCEL_LABEL": {
+    "en": "Cancel",
+    "es": "Cancelar"
+  },
+  "SETTINGS_FORM$END_SESSION_LABEL": {
+    "en": "End Session",
+    "es": "Terminar sesión"
+  },
+  "SETTINGS_FORM$CHANGING_WORKSPACE_WARNING_MESSAGE": {
+    "en": "Changing your settings will clear your workspace and start a new session. Are you sure you want to continue?",
+    "es": "Cambiar tu configuración limpiará tu espacio de trabajo e iniciará una nueva sesión. ¿Estás seguro de continuar?"
+  },
+  "SETTINGS_FORM$ARE_YOU_SURE_LABEL": {
+    "en": "Are you sure?",
+    "es": "¿Estás seguro?"
+  },
+  "SETTINGS_FORM$ALL_INFORMATION_WILL_BE_DELETED_MESSAGE": {
+    "en": "All saved information in your AI settings will be deleted, including any API keys.",
+    "es": "Toda la información guardada en tu configuración de IA será eliminada, incluyendo tus API Keys"
+  },
+  "PROJECT_MENU_DETAILS_PLACEHOLDER$NEW_PROJECT_LABEL": {
+    "en":"New Project",
+    "es":"Nuevo proyecto"
+  },
+  "PROJECT_MENU_DETAILS$AGO_LABEL": {
+    "en":"ago",
+    "es":"atrás"
+  },
   "STATUS$ERROR_LLM_AUTHENTICATION": {
     "en": "Error authenticating with the LLM provider. Please check your API key"
   },