From a085dae2a8db5c76a231fadcbd8401f8b8351a96 Mon Sep 17 00:00:00 2001 From: Jordi Marzo Date: Sat, 13 May 2023 15:44:57 +0200 Subject: [PATCH 1/4] Add i18n module + translations --- package.json | 9 +- src/assets/i18n/en.json | 117 ++++++++++++ src/assets/i18n/fr.json | 117 ++++++++++++ src/components/CreatePromptModal.tsx | 24 +-- src/components/DeleteChatModal.tsx | 21 ++- src/components/DeleteChatsModal.tsx | 12 +- src/components/Layout.tsx | 33 ++-- src/components/SettingsModal.tsx | 96 +++++----- src/i18n.tsx | 22 +++ src/react-i18next.d.tsx | 11 ++ src/routes/ChatRoute.tsx | 44 ++--- src/routes/IndexRoute.tsx | 23 ++- src/static/{config.json => config_en.json} | 2 +- src/static/config_fr.json | 207 +++++++++++++++++++++ src/utils/config.ts | 4 +- tsconfig.json | 3 +- yarn.lock | 36 +++- 17 files changed, 651 insertions(+), 130 deletions(-) create mode 100644 src/assets/i18n/en.json create mode 100644 src/assets/i18n/fr.json create mode 100644 src/i18n.tsx create mode 100644 src/react-i18next.d.tsx rename src/static/{config.json => config_en.json} (99%) create mode 100644 src/static/config_fr.json diff --git a/package.json b/package.json index 93dd9e0..c89d24e 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "@types/react-dom": "^18.0.11", "buffer": "^5.7.1", "parcel": "^2.8.3", - "path-browserify": "^1.0.1", "parcel-reporter-static-files-copy": "^1.5.0", + "path-browserify": "^1.0.1", "process": "^0.11.10" }, "dependencies": { @@ -32,8 +32,8 @@ "@tabler/icons-react": "^2.9.0", "@tanstack/react-location": "^3.7.4", "@types/node": "18.15.0", - "@types/react": "18.0.28", - "@types/react-dom": "18.0.11", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", "dexie": "^3.2.3", "dexie-export-import": "^4.0.6", "dexie-react-hooks": "^1.1.3", @@ -41,6 +41,8 @@ "eslint": "8.36.0", "eslint-config-next": "13.2.4", "gpt-token-utils": "^1.2.0", + "i18next": "^22.4.15", + "i18next-browser-languagedetector": "^7.0.1", "lodash": "^4.17.21", "nanoid": "^4.0.1", "next": "13.2.4", @@ -48,6 +50,7 @@ "openai-ext": "^1.2.6", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^12.2.2", "react-icons": "^4.8.0", "react-markdown": "^8.0.6", "remark-gfm": "^3.0.1", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json new file mode 100644 index 0000000..79b2974 --- /dev/null +++ b/src/assets/i18n/en.json @@ -0,0 +1,117 @@ +{ + "translation": { + "notAnotherUI": "Not just another ChatGPT user-interface!", + "changeOpenAIKey": "Change OpenAI Key", + "enterOpenAIKey": "Enter OpenAI Key", + "downloadDesktopApp": "Download Desktop App", + "freeAndOpenSource": { + "title": "Free and open source", + "description": "This app is provided for free and the source code is available on GitHub." + }, + "privacyFocused": { + "title": "Privacy focused", + "description": "No tracking, no cookies, no bullshit. All your data is stored locally." + }, + "bestExperience": { + "title": "Best experience", + "description": "Crafted with love and care to provide the best experience possible." + }, + "search": "Search", + "lightMode": "Light Mode", + "darkMode": "Dark Mode", + "settings": "Settings", + "database": "Database", + "sourceCode": "Source Code", + "followOnTwitter": "Follow on Twitter", + "giveFeedback": "Give Feedback", + "chats": "Chats", + "newChat": "New Chat", + "prompts": "Prompts", + "newPrompt": "New Prompt", + "savePrompt": "Save Prompt", + "createPrompt": "Create Prompt", + "savedPrompt": { + "title": "Saved", + "message": "Prompt created" + }, + "error": { + "title": "Error" + }, + "networkError": { + "title": "Error", + "message": "No internet connection." + }, + "chatIdError": { + "title": "Error", + "message": "chatId is not defined. Please create a chat to get started." + }, + "apiKeyError": { + "title": "Error", + "message": "OpenAI API Key is not defined. Please set your API Key" + }, + "title": "Title", + "content": "Content", + "save": "save", + "youAre": "You are", + "respondInTone": "Respond in", + "respondInStyle": "Respond in", + "tone": "tone", + "style": "style", + "youAreChatGPT": "You are ChatGPT, a large language model trained by OpenAI.", + "defineTitleChat": "What would be a short and relevant title for this chat ? You must strictly answer with only the title, no other text is allowed.", + "character": "Character", + "toneList": "Tone", + "styleList": "Style", + "format": "Format", + "yourMessageHere": "Your message here...", + "deleteChats": "Delete Chats", + "confirmDeleteChats": "Are you sure you want to delete your chats?", + "deleteChat": "Delete Chat", + "confirmDeleteChat": "Are you sure you want to delete this chat?", + "delete": "Delete", + "deleted": "Deleted", + "chatDeleted": "Chat deleted.", + "removeChatError": { + "title": "Error", + "message": "Can't remove chat. Please refresh the page and try again." + }, + "openAIAPIKey": "OpenAI API Key", + "getYourOpenAIAPIkey": "Get your OpenAI API key", + "APIKeyStoredLocally": "The API Key is stored locally on your browser and never sent anywhere else.", + "openAIType": "OpenAI Type", + "openAIKeySaved": { + "title": "saved", + "message": "Your OpenAI Key has been saved." + }, + "openAITypeSaved": { + "title": "saved", + "message": "Your OpenAI Type has been saved." + }, + "openIATTypeCustom": "Custom (e.g. Azure OpenAI)", + "openAIModel": "OpenAI Model (OpenAI Only)", + "openAIModelSaved": { + "title": "saved", + "message": "Your OpenAI Model has been saved." + }, + "warning": "Warning", + "warningCostMessage": "The displayed cost was not updated yet to reflect the costs for each model. Right now it will always show the cost for GPT-3.5 on OpenAI.", + "openAIAuth": "OpenAI Auth (Custom Only)", + "openAIAuthSaved": { + "title": "saved", + "message": "Your OpenAI Auth has been saved." + }, + "none": "None", + "bearerToken": "Bearer Token", + "apiKey": "API Key", + "openAIBaseSaved": { + "title": "saved", + "message": "Your OpenAI Base has been saved." + }, + "openAIAPIBase": "OpenAI API Base (Custom Only)", + "openAIAPIVersion": "OpenAI API Version (Custom Only)", + "openAIVersionSaved": { + "title": "saved", + "message": "Your OpenAI Version has been saved." + } + } +} \ No newline at end of file diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json new file mode 100644 index 0000000..03cd2e0 --- /dev/null +++ b/src/assets/i18n/fr.json @@ -0,0 +1,117 @@ +{ + "translation": { + "notAnotherUI": "Ce n'est pas juste une autre interface de ChatGPT !", + "changeOpenAIKey": "Modifier la clé API d'OpenAI", + "enterOpenAIKey": "Entrer la clé API d'OpenAI", + "downloadDesktopApp": "Télécharger l'application de bureau", + "freeAndOpenSource": { + "title": "Gratuit et open source", + "description": "Cette application est gratuite et le code source est disponible sur GitHub." + }, + "privacyFocused": { + "title": "Centré sur la vie privée", + "description": "Pas de suivi, pas de cookies, pas de mensonge. Toutes vos données sont stockées localement." + }, + "bestExperience": { + "title": "La meilleure expérience", + "description": "Fabriqué avec amour et soin pour offrir la meilleure expérience possible." + }, + "search": "Rechercher", + "lightMode": "Mode clair", + "darkMode": "Mode sombre", + "settings": "Paramètres", + "database": "Base de données", + "sourceCode": "Code source", + "followOnTwitter": "Suivez-nous sur Twitter", + "giveFeedback": "Donnez votre avis", + "chats": "Conversations", + "newChat": "Nouvelle conversation", + "prompts": "Prompts", + "newPrompt": "Nouveau Prompt", + "savePrompt": "Enregistrer le Prompt", + "createPrompt": "Créer un Prompt", + "savedPrompt": { + "title": "Sauvegardé", + "message": "Prompt créée" + }, + "error": { + "title": "Erreur" + }, + "networkError": { + "title": "Erreur", + "message": "Pas de connexion internet." + }, + "chatIdError": { + "title": "Erreur", + "message": "le ChatId n'est pas défini. Veuillez créer une conversation pour commencer." + }, + "apiKeyError": { + "title": "Erreur", + "message": "La clé API OpenAI n'est pas définie. Veuillez définir votre clé API" + }, + "title": "Titre", + "content": "Contenu", + "save": "Enregistrer", + "youAre": "Tu es", + "respondInTone": "Réponds sur un ton", + "respondInStyle": "Réponds dans un style", + "tone": "", + "style": "", + "youAreChatGPT": "Tu es ChatGPT, un modèle de langage avancé entraîné par OpenAI.", + "defineTitleChat": "Titre court et pertinent pour cette discussion ? Vous devez répondre strictement avec le titre, aucun autre texte n'est autorisé.", + "character": "Personnage", + "toneList": "Ton", + "styleList": "Style", + "format": "Format", + "yourMessageHere": "Votre message ici...", + "deleteChats": "Supprimer les conversations", + "confirmDeleteChats": "Êtes-vous sûr de vouloir supprimer les conversations ?", + "deleteChat": "Supprimer la conversation", + "confirmDeleteChat": "Êtes-vous sûr de vouloir supprimer la conversation ?", + "delete": "Supprimer", + "deleted": "Supprimée", + "chatDeleted": "Conversation supprimée.", + "removeChatError": { + "title": "Erreur", + "message": "Impossible de supprimer la conversation. Veuillez actualiser la page et réessayer." + }, + "openAIAPIKey": "Clé API OpenAI", + "getYourOpenAIAPIkey": "Obtenez votre clé API OpenAI", + "APIKeyStoredLocally": "La clé API est stockée localement sur votre navigateur et n'est jamais envoyée ailleurs.", + "openAIType": "Type d'OpenAI", + "openAIKeySaved": { + "title": "Sauvegardé", + "message": "Votre clé OpenAI a été enregistrée." + }, + "openAITypeSaved": { + "title": "Sauvegardé", + "message": "Votre type d'OpenAI a été enregistré." + }, + "openIATTypeCustom": "Personnalisé (par exemple: Azure OpenAI)", + "openAIModel": "Modèle OpenAI (OpenAI seulement)", + "openAIModelSaved": { + "title": "Sauvegardé", + "message": "Votre modèle OpenAI a été enregistré." + }, + "warning": "Avertissement", + "warningCostMessage": "Le coût affiché n'a pas encore été mis à jour pour refléter les coûts de chaque modèle. Pour l'instant, le coût affiché est toujours celui du modèle GPT-3.5 de l'OpenAI.", + "openAIAuth": "Methode d'authentification OpenAI (personnalisé seulement)", + "openAIAuthSaved": { + "title": "Sauvegardé", + "message": "Votre methode d'authentification OpenAI a été enregistré." + }, + "none": "Aucune", + "bearerToken": "Bearer Token", + "apiKey": "Clé API", + "openAIBaseSaved": { + "title": "Sauvegardé", + "message": "Votre URL d'API OpenAI a été enregistrée." + }, + "openAIAPIBase": "URL d'API OpenAI (personnalisé uniquement)", + "openAIAPIVersion": "Version de l'API OpenAI (personnalisé uniquement)", + "openAIVersionSaved": { + "title": "Sauvegardé", + "message": "Votre version OpenAI a été enregistrée." + } + } +} \ No newline at end of file diff --git a/src/components/CreatePromptModal.tsx b/src/components/CreatePromptModal.tsx index 70bd778..588c964 100644 --- a/src/components/CreatePromptModal.tsx +++ b/src/components/CreatePromptModal.tsx @@ -13,6 +13,8 @@ import { IconPlaylistAdd, IconPlus } from "@tabler/icons-react"; import { nanoid } from "nanoid"; import { useEffect, useState } from "react"; import { db } from "../db"; +import '../i18n' +import {t} from "i18next"; export function CreatePromptModal({ content }: { content?: string }) { const [opened, { open, close }] = useDisclosure(false); @@ -27,17 +29,17 @@ export function CreatePromptModal({ content }: { content?: string }) { return ( <> {content ? ( - + ) : ( )} - +
{ try { @@ -51,22 +53,22 @@ export function CreatePromptModal({ content }: { content?: string }) { createdAt: new Date(), }); notifications.show({ - title: "Saved", - message: "Prompt created", + title: t('savedPrompt.title'), + message: t('savedPrompt.message'), }); close(); } catch (error: any) { if (error.toJSON().message === "Network Error") { notifications.show({ - title: "Error", + title: t('networkError.title'), color: "red", - message: "No internet connection.", + message: t('networkError.message'), }); } const message = error.response?.data?.error?.message; if (message) { notifications.show({ - title: "Error", + title: t('error.title'), color: "red", message, }); @@ -78,14 +80,14 @@ export function CreatePromptModal({ content }: { content?: string }) { > setTitle(event.currentTarget.value)} formNoValidate data-autofocus />