From cfad0deb9835f2ec2250f4dda2fa385f0d57c319 Mon Sep 17 00:00:00 2001 From: Paul Ccari <46382556+paulclindo@users.noreply.github.com> Date: Fri, 14 Jun 2024 21:22:21 -0500 Subject: [PATCH] feat: support i18n (#333) * setup i18n * setup i18n * setup i18n * refactor: i18n * refactor: i18n * fixes * fixes * fix package lock * install i18next * refactor: i18n in visor * fix: remove i18n lib * add readme * add readme * add readme * fix --- apps/shinkai-app/.nvmrc | 1 - apps/shinkai-desktop/.nvmrc | 1 - apps/shinkai-desktop/src/pages/agents.tsx | 58 +++--- .../src/pages/ai-model-installation.tsx | 6 +- .../src/pages/ai-model-locally.tsx | 9 +- apps/shinkai-desktop/src/pages/analytics.tsx | 10 +- .../src/pages/chat/chat-conversation.tsx | 48 +++-- .../src/pages/chat/empty-message.tsx | 13 +- .../shinkai-desktop/src/pages/chat/layout.tsx | 29 +-- .../src/pages/create-agent.tsx | 23 +- apps/shinkai-desktop/src/pages/create-job.tsx | 23 +- .../src/pages/layout/main-layout.tsx | 75 +++---- .../src/pages/layout/simple-layout.tsx | 8 +- .../action-button/action-button.tsx | 7 +- .../src/components/agents/add-agent.tsx | 42 ++-- .../src/components/agents/agents.tsx | 23 +- .../connect-method-qr-code.tsx | 29 +-- .../connect-method-quick-start.tsx | 30 +-- .../connect-method-restore-connection.tsx | 17 +- .../components/create-inbox/create-inbox.tsx | 15 +- .../src/components/create-job/create-job.tsx | 20 +- .../create-registration-code.tsx | 35 ++-- .../edit-inbox-name-dialog.tsx | 14 +- .../components/empty-agents/empty-agents.tsx | 15 +- .../empty-inboxes/empty-inboxes.tsx | 16 +- .../export-connection/export-connection.tsx | 19 +- .../iframe-embeder/installed-popup.tsx | 13 +- .../image-capture/image-capture.tsx | 10 +- .../components/inbox-input/inbox-input.tsx | 5 +- .../src/components/inbox/inbox.tsx | 23 +- .../src/components/inboxes/inboxes.tsx | 12 +- .../installed-popup copy/installed-popup.tsx | 13 +- .../installed-popup/installed-popup.tsx | 13 +- apps/shinkai-visor/src/components/nav/nav.tsx | 73 +++---- .../node-files/vector-fs-general-options.tsx | 7 +- .../src/components/popup/popup.tsx | 16 +- .../src/components/settings/settings.tsx | 48 ++--- .../src/components/setup/setup.tsx | 18 +- .../src/components/welcome/welcome.tsx | 7 +- apps/shinkai-visor/src/lang/en.json | 116 ----------- apps/shinkai-visor/src/lang/es.json | 114 ---------- apps/shinkai-visor/src/lang/intl.ts | 15 -- .../src/service-worker/service-worker.ts | 4 +- libs/shinkai-i18n/.babelrc | 12 ++ libs/shinkai-i18n/.eslintrc.json | 18 ++ libs/shinkai-i18n/README.md | 44 ++++ libs/shinkai-i18n/package.json | 12 ++ libs/shinkai-i18n/project.json | 9 + libs/shinkai-i18n/src/index.ts | 2 + libs/shinkai-i18n/src/lib/init.ts | 24 +++ libs/shinkai-i18n/src/lib/locales/en.json | 161 ++++++++++++++ libs/shinkai-i18n/src/lib/providers.tsx | 8 + libs/shinkai-i18n/src/lib/useTranslation.tsx | 28 +++ libs/shinkai-i18n/tsconfig.json | 20 ++ libs/shinkai-i18n/tsconfig.lib.json | 28 +++ libs/shinkai-i18n/vite.config.ts | 49 +++++ package-lock.json | 197 ++++++------------ package.json | 2 +- tsconfig.base.json | 21 +- 59 files changed, 885 insertions(+), 843 deletions(-) delete mode 100644 apps/shinkai-app/.nvmrc delete mode 100644 apps/shinkai-desktop/.nvmrc delete mode 100644 apps/shinkai-visor/src/lang/en.json delete mode 100644 apps/shinkai-visor/src/lang/es.json delete mode 100644 apps/shinkai-visor/src/lang/intl.ts create mode 100644 libs/shinkai-i18n/.babelrc create mode 100644 libs/shinkai-i18n/.eslintrc.json create mode 100644 libs/shinkai-i18n/README.md create mode 100644 libs/shinkai-i18n/package.json create mode 100644 libs/shinkai-i18n/project.json create mode 100644 libs/shinkai-i18n/src/index.ts create mode 100644 libs/shinkai-i18n/src/lib/init.ts create mode 100644 libs/shinkai-i18n/src/lib/locales/en.json create mode 100644 libs/shinkai-i18n/src/lib/providers.tsx create mode 100644 libs/shinkai-i18n/src/lib/useTranslation.tsx create mode 100644 libs/shinkai-i18n/tsconfig.json create mode 100644 libs/shinkai-i18n/tsconfig.lib.json create mode 100644 libs/shinkai-i18n/vite.config.ts diff --git a/apps/shinkai-app/.nvmrc b/apps/shinkai-app/.nvmrc deleted file mode 100644 index f6ee89c7c..000000000 --- a/apps/shinkai-app/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v18.17 \ No newline at end of file diff --git a/apps/shinkai-desktop/.nvmrc b/apps/shinkai-desktop/.nvmrc deleted file mode 100644 index f6ee89c7c..000000000 --- a/apps/shinkai-desktop/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v18.17 \ No newline at end of file diff --git a/apps/shinkai-desktop/src/pages/agents.tsx b/apps/shinkai-desktop/src/pages/agents.tsx index dff62c778..011946a9e 100644 --- a/apps/shinkai-desktop/src/pages/agents.tsx +++ b/apps/shinkai-desktop/src/pages/agents.tsx @@ -1,5 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { DotsVerticalIcon } from '@radix-ui/react-icons'; +import { useTranslation } from '@shinkai_network/shinkai-i18n'; import { EditAgentFormSchema, editAgentSchema, @@ -39,6 +40,7 @@ import { getModelObject } from './create-agent'; import { SimpleLayout } from './layout/simple-layout'; const AgentsPage = () => { + const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const navigate = useNavigate(); const { agents } = useAgents({ @@ -63,7 +65,7 @@ const AgentsPage = () => { navigate('/add-agent'); }; return ( - +
@@ -81,14 +83,15 @@ const AgentsPage = () => { 🤖 -

No available agents

+

+ {t('agents.notFound.title')} +

- Connect your first agent to start asking Shinkai AI. Try - connecting OpenAI + {t('agents.notFound.description')}

- + ) : ( @@ -123,6 +126,7 @@ function AgentCard({ externalUrl: string; agentApiKey: string; }) { + const { t } = useTranslation(); const [isDeleteAgentDrawerOpen, setIsDeleteAgentDrawerOpen] = React.useState(false); const [isEditAgentDrawerOpen, setIsEditAgentDrawerOpen] = @@ -141,7 +145,7 @@ function AgentCard({ role="button" >
-
+
@@ -169,7 +173,7 @@ function AgentCard({ role="button" tabIndex={0} > - More options + {t('common.moreOptions')}
@@ -179,14 +183,14 @@ function AgentCard({ > {[ { - name: 'Edit', + name: t('common.edit'), icon: , onClick: () => { setIsEditAgentDrawerOpen(true); }, }, { - name: 'Delete', + name: t('common.delete'), icon: , onClick: () => { setIsDeleteAgentDrawerOpen(true); @@ -247,6 +251,7 @@ const EditAgentDrawer = ({ agentExternalUrl: string; agentApiKey: string; }) => { + const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const form = useForm({ @@ -272,10 +277,10 @@ const EditAgentDrawer = ({ const { mutateAsync: updateAgent, isPending } = useUpdateAgent({ onSuccess: () => { onOpenChange(false); - toast.success('AI updated successfully'); + toast.success(t('agents.success.updateAgent')); }, onError: (error) => { - toast.error('Error updating agent', { + toast.error(t('agents.errors.updateAgent'), { description: typeof error === 'string' ? error : error.message, }); }, @@ -313,7 +318,7 @@ const EditAgentDrawer = ({ - Update {agentId}{' '} + {t('common.update')} {agentId}{' '}
@@ -327,7 +332,7 @@ const EditAgentDrawer = ({ disabled name="agentName" render={({ field }) => ( - + )} /> @@ -335,7 +340,10 @@ const EditAgentDrawer = ({ control={form.control} name="externalUrl" render={({ field }) => ( - + )} /> @@ -343,7 +351,7 @@ const EditAgentDrawer = ({ control={form.control} name="apikey" render={({ field }) => ( - + )} /> @@ -351,14 +359,14 @@ const EditAgentDrawer = ({ control={form.control} name="modelCustom" render={({ field }) => ( - + )} /> ( - + )} />
@@ -368,7 +376,7 @@ const EditAgentDrawer = ({ isLoading={isPending} type="submit" > - Save + {t('common.save')} @@ -385,14 +393,15 @@ const RemoveAgentDrawer = ({ onOpenChange: (open: boolean) => void; agentId: string; }) => { + const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const { mutateAsync: deleteAgent, isPending } = useDeleteAgent({ onSuccess: () => { onOpenChange(false); - toast.success('AI deleted successfully'); + toast.success(t('agents.success.deleteAgent')); }, onError: (error) => { - toast.error('Error deleting agent', { + toast.error(t('agents.errors.deleteAgent'), { description: typeof error === 'string' ? error : error.message, }); }, @@ -403,13 +412,12 @@ const RemoveAgentDrawer = ({ - Delete AI + {t('agents.delete.label')} {agentId}{' '}

- Are you sure you want to delete this agent? This action cannot be - undone. + {t('agents.delete.description')}

diff --git a/apps/shinkai-desktop/src/pages/ai-model-installation.tsx b/apps/shinkai-desktop/src/pages/ai-model-installation.tsx index 0bb457669..eb1ff8bc9 100644 --- a/apps/shinkai-desktop/src/pages/ai-model-installation.tsx +++ b/apps/shinkai-desktop/src/pages/ai-model-installation.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from '@shinkai_network/shinkai-i18n'; import { buttonVariants } from '@shinkai_network/shinkai-ui'; import { cn } from '@shinkai_network/shinkai-ui/utils'; import { QueryClientProvider } from '@tanstack/react-query'; @@ -10,11 +11,12 @@ import { shinkaiNodeQueryClient } from '../lib/shinkai-node-manager/shinkai-node import { FixedHeaderLayout } from './layout/simple-layout'; const AIModelInstallation = () => { + const { t } = useTranslation(); return ( @@ -28,7 +30,7 @@ const AIModelInstallation = () => { )} to={{ pathname: '/' }} > - Continue + {t('common.continue')}
diff --git a/apps/shinkai-desktop/src/pages/ai-model-locally.tsx b/apps/shinkai-desktop/src/pages/ai-model-locally.tsx index 6c33cd6b1..ca116896a 100644 --- a/apps/shinkai-desktop/src/pages/ai-model-locally.tsx +++ b/apps/shinkai-desktop/src/pages/ai-model-locally.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from '@shinkai_network/shinkai-i18n'; import { buttonVariants } from '@shinkai_network/shinkai-ui'; import { cn } from '@shinkai_network/shinkai-ui/utils'; import { QueryClientProvider } from '@tanstack/react-query'; @@ -9,15 +10,15 @@ import { shinkaiNodeQueryClient } from '../lib/shinkai-node-manager/shinkai-node import { SubpageLayout } from './layout/simple-layout'; const AgentsLocally = () => { + const { t } = useTranslation(); return (

- After installing AI models on your local machine, they will become - available as AI + {t('agents.localAI.installText')}

@@ -31,7 +32,7 @@ const AgentsLocally = () => { to={{ pathname: '/add-agent' }} > - Manually Add AI + {t('agents.addManually')}
diff --git a/apps/shinkai-desktop/src/pages/analytics.tsx b/apps/shinkai-desktop/src/pages/analytics.tsx index 2eae00b9c..1a0b79b62 100644 --- a/apps/shinkai-desktop/src/pages/analytics.tsx +++ b/apps/shinkai-desktop/src/pages/analytics.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from '@shinkai_network/shinkai-i18n'; import { Button } from '@shinkai_network/shinkai-ui'; import { useNavigate } from 'react-router-dom'; @@ -6,6 +7,7 @@ import OnboardingLayout from './layout/onboarding-layout'; const AnalyticsPage = () => { const navigate = useNavigate(); + const { t } = useTranslation(); const denyAnalytics = useSettings((state) => state.denyAnalytics); const acceptAnalytics = useSettings((state) => state.acceptAnalytics); @@ -13,10 +15,10 @@ const AnalyticsPage = () => {

- Help us improve Shinkai + {t('analytics.title')}

-
    +
      {[ '✅ Always allow you to opt-out via Settings', @@ -57,7 +59,7 @@ const AnalyticsPage = () => { size="lg" variant="ghost" > - No Thanks + {t('common.noThanks')}
diff --git a/apps/shinkai-desktop/src/pages/chat/chat-conversation.tsx b/apps/shinkai-desktop/src/pages/chat/chat-conversation.tsx index 959d1bec1..17d5307e7 100644 --- a/apps/shinkai-desktop/src/pages/chat/chat-conversation.tsx +++ b/apps/shinkai-desktop/src/pages/chat/chat-conversation.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import { useTranslation } from '@shinkai_network/shinkai-i18n'; import { extractErrorPropertyOrContent, extractJobIdFromInbox, @@ -77,7 +78,7 @@ enum ErrorCodes { const ChatConversation = () => { const { captureAnalyticEvent } = useAnalytics(); - + const { t } = useTranslation(); const size = partial({ standard: 'jedec' }); const { inboxId: encodedInboxId = '' } = useParams(); const auth = useAuth((state) => state.auth); @@ -230,12 +231,12 @@ const ChatConversation = () => { {isLimitReachedErrorLastMessage && ( - Limit Reached + + {t('chat.limitReachedTitle')} +
- {/* eslint-disable-next-line react/no-unescaped-entities */} - You've used all of your queries for the month on this model/agent. - Please start a new chat with another agent. + {t('chat.limitReachedDescription')}
@@ -250,7 +251,9 @@ const ChatConversation = () => { name="message" render={({ field }) => ( - Enter message + + {t('chat.enterMessage')} +
@@ -281,7 +284,7 @@ const ChatConversation = () => { className="bg-neutral-900" side="top" > - Upload a File + {t('common.uploadFile')} @@ -297,7 +300,9 @@ const ChatConversation = () => { variant="tertiary" > - Send Message + + {t('chat.sendMessage')} + } disabled={isLoadingMessage} @@ -310,17 +315,17 @@ const ChatConversation = () => { {getFileExt(file?.name) && fileIconMap[getFileExt(file?.name)] ? ( ) : ( )}
- + {file?.name} - + {size(file?.size)}
@@ -358,6 +363,7 @@ const ChatConversation = () => { export default ChatConversation; function AgentSelection() { + const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const currentInbox = useGetCurrentInbox(); const { agents } = useAgents({ @@ -374,7 +380,7 @@ function AgentSelection() { const { mutateAsync: updateAgentInJob } = useUpdateAgentInJob({ onError: (error) => { - toast.error('Failed to update agent', { + toast.error(t('agents.errors.updateAgent'), { description: error.message, }); }, @@ -384,7 +390,7 @@ function AgentSelection() { - + {currentInbox?.agent?.id} @@ -396,7 +402,7 @@ function AgentSelection() { className="bg-neutral-900" side="top" > - Switch AI + {t('agents.switch')} { const currentInbox = useGetCurrentInbox(); - + const { t } = useTranslation(); const hasFolders = (currentInbox?.job_scope?.vector_fs_folders ?? [])?.length > 0; const hasFiles = (currentInbox?.job_scope?.vector_fs_items ?? [])?.length > 0; @@ -491,14 +497,16 @@ export const ConversationHeader = () => { - Conversation Context + {t('chat.context.title')} - List of folders and files used as context for this conversation + {t('chat.context.description')}
{hasFolders && (
- Folders + + {t('common.folders')} +
    {currentInbox?.job_scope?.vector_fs_folders?.map( (folder) => ( @@ -518,7 +526,9 @@ export const ConversationHeader = () => { )} {hasFiles && (
    - Files + + {t('common.files')} +
      {currentInbox?.job_scope?.vector_fs_items?.map((file) => (
    • { const isLocalShinkaiNodeIsUse = useShinkaiNodeManager( (state) => state.isInUse, ); + const { t } = useTranslation(); return (
      @@ -31,10 +33,11 @@ const EmptyMessage = () => { 🤖 -

      Ask Shinkai AI

      +

      + {t('chat.emptyStateTitle')} +

      - Try “How to make a HTTP request in JavaScript” , “Give me the top 10 - rock music in the 80s”, “Explain me how internet works” + {t('chat.emptyStateDescription')}

      @@ -45,7 +48,7 @@ const EmptyMessage = () => { })} to={isLocalShinkaiNodeIsUse ? '/agents-locally' : '/add-agent'} > - Add AI + {t('agents.add')} ) : ( { })} to={CREATE_JOB_PATH} > - Create AI Chat + {t('chat.create')} )}
      diff --git a/apps/shinkai-desktop/src/pages/chat/layout.tsx b/apps/shinkai-desktop/src/pages/chat/layout.tsx index 49f94e71c..328720ee7 100644 --- a/apps/shinkai-desktop/src/pages/chat/layout.tsx +++ b/apps/shinkai-desktop/src/pages/chat/layout.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import { useTranslation } from '@shinkai_network/shinkai-i18n'; // import { PlusCircledIcon } from '@radix-ui/react-icons'; import { SmartInbox } from '@shinkai_network/shinkai-message-ts/models'; import { @@ -59,6 +60,7 @@ const InboxNameInput = ({ inboxId: string; inboxName: string; }) => { + const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const updateInboxNameForm = useForm({ resolver: zodResolver(updateInboxNameFormSchema), @@ -107,7 +109,7 @@ const InboxNameInput = ({ - Update inbox name + {t('inboxes.updateName')} - Save + {t('common.save')} ) : ( )} @@ -157,7 +159,7 @@ const MessageButton = ({ inbox: SmartInbox; }) => { const auth = useAuth((state) => state.auth); - + const { t } = useTranslation(); const inboxId = inbox.inbox_id; const inboxName = inbox.last_message && inbox.custom_name === inbox.inbox_id @@ -267,7 +269,7 @@ const MessageButton = ({ -

      Rename

      +

      {t('common.rename')}

      @@ -288,7 +290,7 @@ const MessageButton = ({ -

      Archive

      +

      {t('chat.archives.archive')}

      @@ -300,6 +302,7 @@ const MessageButton = ({ }; const ChatLayout = () => { + const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const navigate = useNavigate(); const { inboxes } = useGetInboxes( @@ -348,7 +351,7 @@ const ChatLayout = () => { <>
      -

      Chats

      +

      {t('chat.chats')}

      @@ -361,12 +364,12 @@ const ChatLayout = () => { variant="ghost" > - Create AI Chat + {t('chat.create')} -

      Create AI Chat

      +

      {t('chat.create')}

      @@ -380,14 +383,14 @@ const ChatLayout = () => { value="actives" > - Actives + {t('chat.actives.label')} - Archives + {t('chat.archives.label')} @@ -402,7 +405,7 @@ const ChatLayout = () => { )) ) : (

      - No active conversations found.{' '} + {t('chat.actives.notFound')}{' '}

      )}
      @@ -420,7 +423,7 @@ const ChatLayout = () => { )) ) : (

      - No archived conversations found.{' '} + {t('chat.archives.notFound')}{' '}

      )}
      diff --git a/apps/shinkai-desktop/src/pages/create-agent.tsx b/apps/shinkai-desktop/src/pages/create-agent.tsx index 79d2d46b2..dfc517fc8 100644 --- a/apps/shinkai-desktop/src/pages/create-agent.tsx +++ b/apps/shinkai-desktop/src/pages/create-agent.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import { useTranslation } from '@shinkai_network/shinkai-i18n'; import { AgentAPIModel } from '@shinkai_network/shinkai-message-ts/models'; import { addAgentFormDefault, @@ -70,6 +71,7 @@ export const getModelObject = ( }; const CreateAgentPage = () => { + const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const navigate = useNavigate(); const addAgentForm = useForm({ @@ -211,7 +213,7 @@ const CreateAgentPage = () => { }; return ( - +
      { control={addAgentForm.control} name="modelCustom" render={({ field }) => ( - + )} /> ( - + )} /> @@ -329,21 +334,25 @@ const CreateAgentPage = () => { control={addAgentForm.control} name="agentName" render={({ field }) => ( - + )} /> ( - + )} /> ( - + )} />
      @@ -356,7 +365,7 @@ const CreateAgentPage = () => { isLoading={isPending} type="submit" > - Add AI + {t('agents.add')} diff --git a/apps/shinkai-desktop/src/pages/create-job.tsx b/apps/shinkai-desktop/src/pages/create-job.tsx index 0a88a3c15..b21f8545a 100644 --- a/apps/shinkai-desktop/src/pages/create-job.tsx +++ b/apps/shinkai-desktop/src/pages/create-job.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import { useTranslation } from '@shinkai_network/shinkai-i18n'; import { buildInboxIdFromJobId } from '@shinkai_network/shinkai-message-ts/utils'; import { CreateJobFormSchema, @@ -58,6 +59,7 @@ import { useSettings } from '../store/settings'; import { SubpageLayout } from './layout/simple-layout'; const CreateJobPage = () => { + const { t } = useTranslation(); const auth = useAuth((state) => state.auth); const defaulAgentId = useSettings((state) => state.defaultAgentId); const navigate = useNavigate(); @@ -240,7 +242,7 @@ const CreateJobPage = () => { // }, []); return ( - +
      { name="content" render={({ field }) => ( - Tell us the job you want to do + {t('chat.form.message')}