diff --git a/app.dockerfile b/app.dockerfile index c3c0fd0..605957e 100644 --- a/app.dockerfile +++ b/app.dockerfile @@ -7,6 +7,7 @@ RUN yarn install --frozen-lockfile --network-timeout 600000 COPY tsconfig.json next.config.mjs next-env.d.ts postcss.config.js drizzle.config.ts tailwind.config.ts ./ COPY src ./src +COPY messages ./messages COPY public ./public RUN mkdir -p /home/perplexica/data diff --git a/messages/de.json b/messages/de.json new file mode 100644 index 0000000..26627c1 --- /dev/null +++ b/messages/de.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - Mit dem Internet chatten", + "description": "Perplexica ist ein KI-Chatbot, der mit dem Internet verbunden ist." + }, + "manifest": { + "name": "Perplexica - Mit dem Internet chatten", + "shortName": "Perplexica", + "description": "Perplexica ist ein KI-Chatbot, der mit dem Internet verbunden ist." + }, + "navigation": { + "home": "Startseite", + "discover": "Entdecken", + "library": "Bibliothek", + "settings": "Einstellungen" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "Exportiert am:", + "citations": "Quellen:", + "user": "Benutzer", + "assistant": "Assistent", + "errors": { + "noChatModelsAvailable": "Keine Chat-Modelle verfügbar", + "chatProviderNotConfigured": "Es scheint, dass kein Chat-Model-Anbieter konfiguriert ist. Bitte konfigurieren Sie diesen auf der Einstellungsseite oder in der Konfigurationsdatei.", + "noEmbeddingModelsAvailable": "Keine Embedding-Modelle verfügbar", + "cannotSendBeforeConfigReady": "Nachricht kann nicht gesendet werden, bevor die Konfiguration abgeschlossen ist", + "failedToDeleteChat": "Chat konnte nicht gelöscht werden" + } + }, + "navbar": { + "exportAsMarkdown": "Als Markdown exportieren", + "exportAsPDF": "Als PDF exportieren" + }, + "export": { + "chatExportTitle": "Chat-Export: {title}" + }, + "weather": { + "conditions": { + "clear": "Klar", + "mainlyClear": "Überwiegend klar", + "partlyCloudy": "Teilweise bewölkt", + "cloudy": "Bewölkt", + "fog": "Nebel", + "lightDrizzle": "Leichter Nieselregen", + "moderateDrizzle": "Mäßiger Nieselregen", + "denseDrizzle": "Starker Nieselregen", + "lightFreezingDrizzle": "Leichter gefrierender Nieselregen", + "denseFreezingDrizzle": "Starker gefrierender Nieselregen", + "slightRain": "Leichter Regen", + "moderateRain": "Mäßiger Regen", + "heavyRain": "Starker Regen", + "lightFreezingRain": "Leichter gefrierender Regen", + "heavyFreezingRain": "Starker gefrierender Regen", + "slightSnowFall": "Leichter Schneefall", + "moderateSnowFall": "Mäßiger Schneefall", + "heavySnowFall": "Starker Schneefall", + "snow": "Schneefall", + "slightRainShowers": "Leichte Regenschauer", + "moderateRainShowers": "Regenschauer", + "heavyRainShowers": "Starke Regenschauer", + "slightSnowShowers": "Leichte Schneeschauer", + "moderateSnowShowers": "Schneeschauer", + "heavySnowShowers": "Starke Schneeschauer", + "thunderstorm": "Gewitter", + "thunderstormSlightHail": "Gewitter mit kleinem Hagel", + "thunderstormHeavyHail": "Gewitter mit starkem Hagel" + } + }, + "pages": { + "home": { + "title": "Chat - Perplexica", + "description": "Chatten Sie mit dem Internet, chatten Sie mit Perplexica." + }, + "discover": { + "title": "Entdecken", + "topics": { + "tech": "Technik & Wissenschaft", + "finance": "Finanzen", + "art": "Kunst & Kultur", + "sports": "Sport", + "entertainment": "Unterhaltung" + }, + "errorFetchingData": "Fehler beim Abrufen der Daten" + }, + "library": { + "title": "Bibliothek", + "empty": "Keine Chats gefunden.", + "ago": "vor {time}" + }, + "settings": { + "title": "Einstellungen", + "sections": { + "preferences": "Voreinstellungen", + "automaticSearch": "Automatische Suche", + "systemInstructions": "Systemanweisungen", + "modelSettings": "Modelleinstellungen", + "apiKeys": "API-Schlüssel" + }, + "preferences": { + "theme": "Theme", + "measurementUnits": "Maßeinheiten", + "language": "Sprache", + "metric": "Metrisch", + "imperial": "Imperial" + }, + "automaticSearch": { + "image": { + "title": "Automatische Bildsuche", + "desc": "Automatisch relevante Bilder in Chat-Antworten suchen" + }, + "video": { + "title": "Automatische Videosuche", + "desc": "Automatisch relevante Videos in Chat-Antworten suchen" + } + }, + "model": { + "chatProvider": "Anbieter des Chat-Modells", + "chat": "Chat-Modell", + "noModels": "Keine Modelle verfügbar", + "invalidProvider": "Ungültiger Anbieter, bitte prüfen Sie die Backend-Logs", + "custom": { + "modelName": "Modellname", + "apiKey": "Benutzerdefinierter OpenAI API-Schlüssel", + "baseUrl": "Benutzerdefinierte OpenAI-Basis-URL" + } + }, + "embedding": { + "provider": "Anbieter des Embedding-Modells", + "model": "Embedding-Modell" + }, + "api": { + "openaiApiKey": "OpenAI API-Schlüssel", + "ollamaApiUrl": "Ollama API-URL", + "groqApiKey": "GROQ API-Schlüssel", + "anthropicApiKey": "Anthropic API-Schlüssel", + "geminiApiKey": "Gemini API-Schlüssel", + "deepseekApiKey": "Deepseek API-Schlüssel", + "aimlApiKey": "AI/ML API-Schlüssel", + "lmStudioApiUrl": "LM Studio API-URL" + }, + "systemInstructions": { + "placeholder": "Besondere Anweisungen für das LLM" + } + } + }, + "components": { + "common": { + "viewMore": "Weitere {count} anzeigen" + }, + "messageInput": { + "placeholder": "Rückfrage stellen" + }, + "messageBox": { + "sources": "Quellen", + "answer": "Antwort", + "related": "Ähnlich" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "Angehängte Dateien", + "add": "Hinzufügen", + "clear": "Leeren", + "attach": "Anhängen", + "uploading": "Hochladen...", + "files": "{count} Dateien" + }, + "focus": { + "button": "Fokus", + "modes": { + "webSearch": { + "title": "Alle", + "description": "Im gesamten Internet suchen" + }, + "academicSearch": { + "title": "Wissenschaftlich", + "description": "In veröffentlichten wissenschaftlichen Arbeiten suchen" + }, + "writingAssistant": { + "title": "Schreiben", + "description": "Ohne Websuche chatten" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "Rechenwissen-Engine" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "Videos suchen und ansehen" + }, + "redditSearch": { + "title": "Reddit", + "description": "Diskussionen und Meinungen suchen" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "Geschwindigkeit", + "description": "Geschwindigkeit priorisieren und schnellstmögliche Antwort erhalten" + }, + "balanced": { + "title": "Ausgewogen", + "description": "Das richtige Gleichgewicht zwischen Geschwindigkeit und Genauigkeit finden" + }, + "quality": { + "title": "Qualität (bald)", + "description": "Die gründlichste und genaueste Antwort erhalten" + } + } + }, + "messageActions": { + "rewrite": "Neu formulieren" + }, + "searchImages": { + "searchButton": "Bilder suchen" + }, + "searchVideos": { + "searchButton": "Videos suchen", + "badge": "Video" + }, + "weather": { + "humidity": "Luftfeuchtigkeit", + "now": "Jetzt" + }, + "newsArticleWidget": { + "error": "News konnten nicht geladen werden." + }, + "emptyChat": { + "title": "Forschung beginnt hier." + }, + "emptyChatMessageInput": { + "placeholder": "Frag mich alles..." + }, + "deleteChat": { + "title": "Löschbestätigung", + "description": "Möchten Sie diesen Chat wirklich löschen?", + "cancel": "Abbrechen", + "delete": "Löschen" + }, + "themeSwitcher": { + "options": { + "light": "Hell", + "dark": "Dunkel" + } + } + } +} diff --git a/messages/en-GB.json b/messages/en-GB.json new file mode 100644 index 0000000..3b31c30 --- /dev/null +++ b/messages/en-GB.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - Chat with the internet", + "description": "Perplexica is an AI powered chatbot that is connected to the internet." + }, + "manifest": { + "name": "Perplexica - Chat with the internet", + "shortName": "Perplexica", + "description": "Perplexica is an AI powered chatbot that is connected to the internet." + }, + "navigation": { + "home": "Home", + "discover": "Discover", + "library": "Library", + "settings": "Settings" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "Exported on:", + "citations": "Citations:", + "user": "User", + "assistant": "Assistant", + "errors": { + "noChatModelsAvailable": "No chat models available", + "chatProviderNotConfigured": "Looks like you haven't configured any chat model providers. Please configure them from the settings page or the config file.", + "noEmbeddingModelsAvailable": "No embedding models available", + "cannotSendBeforeConfigReady": "Cannot send message before the configuration is ready", + "failedToDeleteChat": "Failed to delete chat" + } + }, + "navbar": { + "exportAsMarkdown": "Export as Markdown", + "exportAsPDF": "Export as PDF" + }, + "export": { + "chatExportTitle": "Chat Export: {title}" + }, + "weather": { + "conditions": { + "clear": "Clear", + "mainlyClear": "Mainly Clear", + "partlyCloudy": "Partly Cloudy", + "cloudy": "Cloudy", + "fog": "Fog", + "lightDrizzle": "Light Drizzle", + "moderateDrizzle": "Moderate Drizzle", + "denseDrizzle": "Dense Drizzle", + "lightFreezingDrizzle": "Light Freezing Drizzle", + "denseFreezingDrizzle": "Dense Freezing Drizzle", + "slightRain": "Slight Rain", + "moderateRain": "Moderate Rain", + "heavyRain": "Heavy Rain", + "lightFreezingRain": "Light Freezing Rain", + "heavyFreezingRain": "Heavy Freezing Rain", + "slightSnowFall": "Slight Snow Fall", + "moderateSnowFall": "Moderate Snow Fall", + "heavySnowFall": "Heavy Snow Fall", + "snow": "Snow", + "slightRainShowers": "Slight Rain Showers", + "moderateRainShowers": "Moderate Rain Showers", + "heavyRainShowers": "Heavy Rain Showers", + "slightSnowShowers": "Slight Snow Showers", + "moderateSnowShowers": "Moderate Snow Showers", + "heavySnowShowers": "Heavy Snow Showers", + "thunderstorm": "Thunderstorm", + "thunderstormSlightHail": "Thunderstorm with Slight Hail", + "thunderstormHeavyHail": "Thunderstorm with Heavy Hail" + } + }, + "pages": { + "home": { + "title": "Chat - Perplexica", + "description": "Chat with the internet, chat with Perplexica." + }, + "discover": { + "title": "Discover", + "topics": { + "tech": "Tech & Science", + "finance": "Finance", + "art": "Art & Culture", + "sports": "Sports", + "entertainment": "Entertainment" + }, + "errorFetchingData": "Error fetching data" + }, + "library": { + "title": "Library", + "empty": "No chats found.", + "ago": "{time} Ago" + }, + "settings": { + "title": "Settings", + "sections": { + "preferences": "Preferences", + "automaticSearch": "Automatic Search", + "systemInstructions": "System Instructions", + "modelSettings": "Model Settings", + "apiKeys": "API Keys" + }, + "preferences": { + "theme": "Theme", + "measurementUnits": "Measurement Units", + "language": "Language", + "metric": "Metric", + "imperial": "Imperial" + }, + "automaticSearch": { + "image": { + "title": "Automatic Image Search", + "desc": "Automatically search for relevant images in chat responses" + }, + "video": { + "title": "Automatic Video Search", + "desc": "Automatically search for relevant videos in chat responses" + } + }, + "model": { + "chatProvider": "Chat Model Provider", + "chat": "Chat Model", + "noModels": "No models available", + "invalidProvider": "Invalid provider, please check backend logs", + "custom": { + "modelName": "Model Name", + "apiKey": "Custom OpenAI API Key", + "baseUrl": "Custom OpenAI Base URL" + } + }, + "embedding": { + "provider": "Embedding Model Provider", + "model": "Embedding Model" + }, + "api": { + "openaiApiKey": "OpenAI API Key", + "ollamaApiUrl": "Ollama API URL", + "groqApiKey": "GROQ API Key", + "anthropicApiKey": "Anthropic API Key", + "geminiApiKey": "Gemini API Key", + "deepseekApiKey": "Deepseek API Key", + "aimlApiKey": "AI/ML API Key", + "lmStudioApiUrl": "LM Studio API URL" + }, + "systemInstructions": { + "placeholder": "Any special instructions for the LLM" + } + } + }, + "components": { + "common": { + "viewMore": "View {count} more" + }, + "messageInput": { + "placeholder": "Ask a follow-up" + }, + "messageBox": { + "sources": "Sources", + "answer": "Answer", + "related": "Related" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "Attached files", + "add": "Add", + "clear": "Clear", + "attach": "Attach", + "uploading": "Uploading...", + "files": "{count} files" + }, + "focus": { + "button": "Focus", + "modes": { + "webSearch": { + "title": "All", + "description": "Searches across all of the internet" + }, + "academicSearch": { + "title": "Academic", + "description": "Search in published academic papers" + }, + "writingAssistant": { + "title": "Writing", + "description": "Chat without searching the web" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "Computational knowledge engine" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "Search and watch videos" + }, + "redditSearch": { + "title": "Reddit", + "description": "Search for discussions and opinions" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "Speed", + "description": "Prioritize speed and get the quickest possible answer." + }, + "balanced": { + "title": "Balanced", + "description": "Find the right balance between speed and accuracy" + }, + "quality": { + "title": "Quality (Soon)", + "description": "Get the most thorough and accurate answer" + } + } + }, + "messageActions": { + "rewrite": "Rewrite" + }, + "searchImages": { + "searchButton": "Search images" + }, + "searchVideos": { + "searchButton": "Search videos", + "badge": "Video" + }, + "weather": { + "humidity": "Humidity", + "now": "Now" + }, + "newsArticleWidget": { + "error": "Could not load news." + }, + "emptyChat": { + "title": "Research begins here." + }, + "emptyChatMessageInput": { + "placeholder": "Ask anything..." + }, + "deleteChat": { + "title": "Delete Confirmation", + "description": "Are you sure you want to delete this chat?", + "cancel": "Cancel", + "delete": "Delete" + }, + "themeSwitcher": { + "options": { + "light": "Light", + "dark": "Dark" + } + } + } +} diff --git a/messages/en-US.json b/messages/en-US.json new file mode 100644 index 0000000..3b31c30 --- /dev/null +++ b/messages/en-US.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - Chat with the internet", + "description": "Perplexica is an AI powered chatbot that is connected to the internet." + }, + "manifest": { + "name": "Perplexica - Chat with the internet", + "shortName": "Perplexica", + "description": "Perplexica is an AI powered chatbot that is connected to the internet." + }, + "navigation": { + "home": "Home", + "discover": "Discover", + "library": "Library", + "settings": "Settings" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "Exported on:", + "citations": "Citations:", + "user": "User", + "assistant": "Assistant", + "errors": { + "noChatModelsAvailable": "No chat models available", + "chatProviderNotConfigured": "Looks like you haven't configured any chat model providers. Please configure them from the settings page or the config file.", + "noEmbeddingModelsAvailable": "No embedding models available", + "cannotSendBeforeConfigReady": "Cannot send message before the configuration is ready", + "failedToDeleteChat": "Failed to delete chat" + } + }, + "navbar": { + "exportAsMarkdown": "Export as Markdown", + "exportAsPDF": "Export as PDF" + }, + "export": { + "chatExportTitle": "Chat Export: {title}" + }, + "weather": { + "conditions": { + "clear": "Clear", + "mainlyClear": "Mainly Clear", + "partlyCloudy": "Partly Cloudy", + "cloudy": "Cloudy", + "fog": "Fog", + "lightDrizzle": "Light Drizzle", + "moderateDrizzle": "Moderate Drizzle", + "denseDrizzle": "Dense Drizzle", + "lightFreezingDrizzle": "Light Freezing Drizzle", + "denseFreezingDrizzle": "Dense Freezing Drizzle", + "slightRain": "Slight Rain", + "moderateRain": "Moderate Rain", + "heavyRain": "Heavy Rain", + "lightFreezingRain": "Light Freezing Rain", + "heavyFreezingRain": "Heavy Freezing Rain", + "slightSnowFall": "Slight Snow Fall", + "moderateSnowFall": "Moderate Snow Fall", + "heavySnowFall": "Heavy Snow Fall", + "snow": "Snow", + "slightRainShowers": "Slight Rain Showers", + "moderateRainShowers": "Moderate Rain Showers", + "heavyRainShowers": "Heavy Rain Showers", + "slightSnowShowers": "Slight Snow Showers", + "moderateSnowShowers": "Moderate Snow Showers", + "heavySnowShowers": "Heavy Snow Showers", + "thunderstorm": "Thunderstorm", + "thunderstormSlightHail": "Thunderstorm with Slight Hail", + "thunderstormHeavyHail": "Thunderstorm with Heavy Hail" + } + }, + "pages": { + "home": { + "title": "Chat - Perplexica", + "description": "Chat with the internet, chat with Perplexica." + }, + "discover": { + "title": "Discover", + "topics": { + "tech": "Tech & Science", + "finance": "Finance", + "art": "Art & Culture", + "sports": "Sports", + "entertainment": "Entertainment" + }, + "errorFetchingData": "Error fetching data" + }, + "library": { + "title": "Library", + "empty": "No chats found.", + "ago": "{time} Ago" + }, + "settings": { + "title": "Settings", + "sections": { + "preferences": "Preferences", + "automaticSearch": "Automatic Search", + "systemInstructions": "System Instructions", + "modelSettings": "Model Settings", + "apiKeys": "API Keys" + }, + "preferences": { + "theme": "Theme", + "measurementUnits": "Measurement Units", + "language": "Language", + "metric": "Metric", + "imperial": "Imperial" + }, + "automaticSearch": { + "image": { + "title": "Automatic Image Search", + "desc": "Automatically search for relevant images in chat responses" + }, + "video": { + "title": "Automatic Video Search", + "desc": "Automatically search for relevant videos in chat responses" + } + }, + "model": { + "chatProvider": "Chat Model Provider", + "chat": "Chat Model", + "noModels": "No models available", + "invalidProvider": "Invalid provider, please check backend logs", + "custom": { + "modelName": "Model Name", + "apiKey": "Custom OpenAI API Key", + "baseUrl": "Custom OpenAI Base URL" + } + }, + "embedding": { + "provider": "Embedding Model Provider", + "model": "Embedding Model" + }, + "api": { + "openaiApiKey": "OpenAI API Key", + "ollamaApiUrl": "Ollama API URL", + "groqApiKey": "GROQ API Key", + "anthropicApiKey": "Anthropic API Key", + "geminiApiKey": "Gemini API Key", + "deepseekApiKey": "Deepseek API Key", + "aimlApiKey": "AI/ML API Key", + "lmStudioApiUrl": "LM Studio API URL" + }, + "systemInstructions": { + "placeholder": "Any special instructions for the LLM" + } + } + }, + "components": { + "common": { + "viewMore": "View {count} more" + }, + "messageInput": { + "placeholder": "Ask a follow-up" + }, + "messageBox": { + "sources": "Sources", + "answer": "Answer", + "related": "Related" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "Attached files", + "add": "Add", + "clear": "Clear", + "attach": "Attach", + "uploading": "Uploading...", + "files": "{count} files" + }, + "focus": { + "button": "Focus", + "modes": { + "webSearch": { + "title": "All", + "description": "Searches across all of the internet" + }, + "academicSearch": { + "title": "Academic", + "description": "Search in published academic papers" + }, + "writingAssistant": { + "title": "Writing", + "description": "Chat without searching the web" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "Computational knowledge engine" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "Search and watch videos" + }, + "redditSearch": { + "title": "Reddit", + "description": "Search for discussions and opinions" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "Speed", + "description": "Prioritize speed and get the quickest possible answer." + }, + "balanced": { + "title": "Balanced", + "description": "Find the right balance between speed and accuracy" + }, + "quality": { + "title": "Quality (Soon)", + "description": "Get the most thorough and accurate answer" + } + } + }, + "messageActions": { + "rewrite": "Rewrite" + }, + "searchImages": { + "searchButton": "Search images" + }, + "searchVideos": { + "searchButton": "Search videos", + "badge": "Video" + }, + "weather": { + "humidity": "Humidity", + "now": "Now" + }, + "newsArticleWidget": { + "error": "Could not load news." + }, + "emptyChat": { + "title": "Research begins here." + }, + "emptyChatMessageInput": { + "placeholder": "Ask anything..." + }, + "deleteChat": { + "title": "Delete Confirmation", + "description": "Are you sure you want to delete this chat?", + "cancel": "Cancel", + "delete": "Delete" + }, + "themeSwitcher": { + "options": { + "light": "Light", + "dark": "Dark" + } + } + } +} diff --git a/messages/fr-CA.json b/messages/fr-CA.json new file mode 100644 index 0000000..ba415f1 --- /dev/null +++ b/messages/fr-CA.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - Jaser avec Internet", + "description": "Perplexica est un chatbot IA branché sur Internet." + }, + "manifest": { + "name": "Perplexica - Jaser avec Internet", + "shortName": "Perplexica", + "description": "Perplexica est un chatbot IA branché sur Internet." + }, + "navigation": { + "home": "Accueil", + "discover": "Découvrir", + "library": "Bibliothèque", + "settings": "Paramètres" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "Exporté le :", + "citations": "Citations :", + "user": "Utilisateur", + "assistant": "Assistant", + "errors": { + "noChatModelsAvailable": "Aucun modèle de chat disponible", + "chatProviderNotConfigured": "Aucun fournisseur de modèle de chat n'est configuré. Veuillez le configurer à partir de la page des paramètres ou du fichier de configuration.", + "noEmbeddingModelsAvailable": "Aucun modèle d'embedding disponible", + "cannotSendBeforeConfigReady": "Impossible d'envoyer un message avant la fin de la configuration", + "failedToDeleteChat": "Échec de la suppression du chat" + } + }, + "navbar": { + "exportAsMarkdown": "Exporter en Markdown", + "exportAsPDF": "Exporter en PDF" + }, + "export": { + "chatExportTitle": "Export de conversation : {title}" + }, + "weather": { + "conditions": { + "clear": "Dégagé", + "mainlyClear": "Plutôt dégagé", + "partlyCloudy": "Partiellement nuageux", + "cloudy": "Nuageux", + "fog": "Brouillard", + "lightDrizzle": "Bruine faible", + "moderateDrizzle": "Bruine modérée", + "denseDrizzle": "Bruine forte", + "lightFreezingDrizzle": "Bruine verglaçante faible", + "denseFreezingDrizzle": "Bruine verglaçante forte", + "slightRain": "Pluie faible", + "moderateRain": "Pluie modérée", + "heavyRain": "Pluie forte", + "lightFreezingRain": "Pluie verglaçante faible", + "heavyFreezingRain": "Pluie verglaçante forte", + "slightSnowFall": "Neige faible", + "moderateSnowFall": "Neige modérée", + "heavySnowFall": "Fortes chutes de neige", + "snow": "Chute de neige", + "slightRainShowers": "Averses faibles", + "moderateRainShowers": "Averses", + "heavyRainShowers": "Fortes averses", + "slightSnowShowers": "Averses de neige faibles", + "moderateSnowShowers": "Averses de neige", + "heavySnowShowers": "Fortes averses de neige", + "thunderstorm": "Orage", + "thunderstormSlightHail": "Orage avec petites grêles", + "thunderstormHeavyHail": "Orage avec fortes grêles" + } + }, + "pages": { + "home": { + "title": "Chat - Perplexica", + "description": "Jasez avec Internet, jasez avec Perplexica." + }, + "discover": { + "title": "Découvrir", + "topics": { + "tech": "Tech et science", + "finance": "Finance", + "art": "Art et culture", + "sports": "Sports", + "entertainment": "Divertissement" + }, + "errorFetchingData": "Erreur lors de la récupération des données" + }, + "library": { + "title": "Bibliothèque", + "empty": "Aucune conversation trouvée.", + "ago": "Il y a {time}" + }, + "settings": { + "title": "Paramètres", + "sections": { + "preferences": "Préférences", + "automaticSearch": "Recherche automatique", + "systemInstructions": "Instructions système", + "modelSettings": "Paramètres du modèle", + "apiKeys": "Clés API" + }, + "preferences": { + "theme": "Thème", + "measurementUnits": "Unités de mesure", + "language": "Langue", + "metric": "Métrique", + "imperial": "Impérial" + }, + "automaticSearch": { + "image": { + "title": "Recherche d'images automatique", + "desc": "Rechercher automatiquement des images pertinentes dans les réponses du chat" + }, + "video": { + "title": "Recherche de vidéos automatique", + "desc": "Rechercher automatiquement des vidéos pertinentes dans les réponses du chat" + } + }, + "model": { + "chatProvider": "Fournisseur du modèle de chat", + "chat": "Modèle de chat", + "noModels": "Aucun modèle disponible", + "invalidProvider": "Fournisseur invalide, veuillez vérifier les journaux du backend", + "custom": { + "modelName": "Nom du modèle", + "apiKey": "Clé API OpenAI personnalisée", + "baseUrl": "URL de base OpenAI personnalisée" + } + }, + "embedding": { + "provider": "Fournisseur du modèle d'embedding", + "model": "Modèle d'embedding" + }, + "api": { + "openaiApiKey": "Clé API OpenAI", + "ollamaApiUrl": "URL de l'API Ollama", + "groqApiKey": "Clé API GROQ", + "anthropicApiKey": "Clé API Anthropic", + "geminiApiKey": "Clé API Gemini", + "deepseekApiKey": "Clé API Deepseek", + "aimlApiKey": "Clé API AI/ML", + "lmStudioApiUrl": "URL de l'API LM Studio" + }, + "systemInstructions": { + "placeholder": "Toute instruction spécifique pour le LLM" + } + } + }, + "components": { + "common": { + "viewMore": "Voir {count} de plus" + }, + "messageInput": { + "placeholder": "Poser une question de suivi" + }, + "messageBox": { + "sources": "Sources", + "answer": "Réponse", + "related": "Connexe" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "Fichiers joints", + "add": "Ajouter", + "clear": "Effacer", + "attach": "Joindre", + "uploading": "Téléversement...", + "files": "{count} fichiers" + }, + "focus": { + "button": "Focus", + "modes": { + "webSearch": { + "title": "Tous", + "description": "Recherche sur tout Internet" + }, + "academicSearch": { + "title": "Académique", + "description": "Recherche dans des articles académiques publiés" + }, + "writingAssistant": { + "title": "Rédaction", + "description": "Discuter sans rechercher sur le web" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "Moteur de connaissances calculatoires" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "Rechercher et regarder des vidéos" + }, + "redditSearch": { + "title": "Reddit", + "description": "Rechercher des discussions et des opinions" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "Vitesse", + "description": "Prioriser la vitesse pour obtenir la réponse la plus rapide" + }, + "balanced": { + "title": "Équilibré", + "description": "Trouver un équilibre entre vitesse et précision" + }, + "quality": { + "title": "Qualité (bientôt)", + "description": "Obtenir la réponse la plus complète et la plus précise" + } + } + }, + "messageActions": { + "rewrite": "Réécrire" + }, + "searchImages": { + "searchButton": "Rechercher des images" + }, + "searchVideos": { + "searchButton": "Rechercher des vidéos", + "badge": "Vidéo" + }, + "weather": { + "humidity": "Humidité", + "now": "Maintenant" + }, + "newsArticleWidget": { + "error": "Impossible de charger les nouvelles." + }, + "emptyChat": { + "title": "La recherche commence ici." + }, + "emptyChatMessageInput": { + "placeholder": "Demandez-moi n'importe quoi..." + }, + "deleteChat": { + "title": "Confirmation de suppression", + "description": "Voulez-vous vraiment supprimer cette conversation?", + "cancel": "Annuler", + "delete": "Supprimer" + }, + "themeSwitcher": { + "options": { + "light": "Clair", + "dark": "Sombre" + } + } + } +} diff --git a/messages/fr-FR.json b/messages/fr-FR.json new file mode 100644 index 0000000..d3d179b --- /dev/null +++ b/messages/fr-FR.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - Discuter avec Internet", + "description": "Perplexica est un chatbot IA connecté à Internet." + }, + "manifest": { + "name": "Perplexica - Discuter avec Internet", + "shortName": "Perplexica", + "description": "Perplexica est un chatbot IA connecté à Internet." + }, + "navigation": { + "home": "Accueil", + "discover": "Découvrir", + "library": "Bibliothèque", + "settings": "Paramètres" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "Exporté le :", + "citations": "Citations :", + "user": "Utilisateur", + "assistant": "Assistant", + "errors": { + "noChatModelsAvailable": "Aucun modèle de chat disponible", + "chatProviderNotConfigured": "Aucun fournisseur de modèle de chat n'est configuré. Merci de le configurer depuis la page des paramètres ou le fichier de configuration.", + "noEmbeddingModelsAvailable": "Aucun modèle d'embedding disponible", + "cannotSendBeforeConfigReady": "Impossible d'envoyer un message avant la fin de la configuration", + "failedToDeleteChat": "Échec de la suppression du chat" + } + }, + "navbar": { + "exportAsMarkdown": "Exporter en Markdown", + "exportAsPDF": "Exporter en PDF" + }, + "export": { + "chatExportTitle": "Export de conversation : {title}" + }, + "weather": { + "conditions": { + "clear": "Dégagé", + "mainlyClear": "Plutôt dégagé", + "partlyCloudy": "Partiellement nuageux", + "cloudy": "Nuageux", + "fog": "Brouillard", + "lightDrizzle": "Bruine faible", + "moderateDrizzle": "Bruine modérée", + "denseDrizzle": "Bruine forte", + "lightFreezingDrizzle": "Bruine verglaçante faible", + "denseFreezingDrizzle": "Bruine verglaçante forte", + "slightRain": "Pluie faible", + "moderateRain": "Pluie modérée", + "heavyRain": "Pluie forte", + "lightFreezingRain": "Pluie verglaçante faible", + "heavyFreezingRain": "Pluie verglaçante forte", + "slightSnowFall": "Neige faible", + "moderateSnowFall": "Neige modérée", + "heavySnowFall": "Fortes chutes de neige", + "snow": "Chute de neige", + "slightRainShowers": "Averses faibles", + "moderateRainShowers": "Averses", + "heavyRainShowers": "Fortes averses", + "slightSnowShowers": "Averses de neige faibles", + "moderateSnowShowers": "Averses de neige", + "heavySnowShowers": "Fortes averses de neige", + "thunderstorm": "Orage", + "thunderstormSlightHail": "Orage avec petites grêles", + "thunderstormHeavyHail": "Orage avec fortes grêles" + } + }, + "pages": { + "home": { + "title": "Chat - Perplexica", + "description": "Discutez avec Internet, discutez avec Perplexica." + }, + "discover": { + "title": "Découvrir", + "topics": { + "tech": "Tech et science", + "finance": "Finance", + "art": "Art et culture", + "sports": "Sports", + "entertainment": "Divertissement" + }, + "errorFetchingData": "Erreur lors de la récupération des données" + }, + "library": { + "title": "Bibliothèque", + "empty": "Aucune conversation trouvée.", + "ago": "Il y a {time}" + }, + "settings": { + "title": "Paramètres", + "sections": { + "preferences": "Préférences", + "automaticSearch": "Recherche automatique", + "systemInstructions": "Instructions système", + "modelSettings": "Paramètres du modèle", + "apiKeys": "Clés API" + }, + "preferences": { + "theme": "Thème", + "measurementUnits": "Unités de mesure", + "language": "Langue", + "metric": "Métrique", + "imperial": "Impérial" + }, + "automaticSearch": { + "image": { + "title": "Recherche d'images automatique", + "desc": "Rechercher automatiquement des images pertinentes dans les réponses du chat" + }, + "video": { + "title": "Recherche de vidéos automatique", + "desc": "Rechercher automatiquement des vidéos pertinentes dans les réponses du chat" + } + }, + "model": { + "chatProvider": "Fournisseur du modèle de chat", + "chat": "Modèle de chat", + "noModels": "Aucun modèle disponible", + "invalidProvider": "Fournisseur invalide, veuillez vérifier les logs backend", + "custom": { + "modelName": "Nom du modèle", + "apiKey": "Clé API OpenAI personnalisée", + "baseUrl": "URL de base OpenAI personnalisée" + } + }, + "embedding": { + "provider": "Fournisseur du modèle d'embedding", + "model": "Modèle d'embedding" + }, + "api": { + "openaiApiKey": "Clé API OpenAI", + "ollamaApiUrl": "URL de l'API Ollama", + "groqApiKey": "Clé API GROQ", + "anthropicApiKey": "Clé API Anthropic", + "geminiApiKey": "Clé API Gemini", + "deepseekApiKey": "Clé API Deepseek", + "aimlApiKey": "Clé API AI/ML", + "lmStudioApiUrl": "URL de l'API LM Studio" + }, + "systemInstructions": { + "placeholder": "Toute instruction spécifique pour le LLM" + } + } + }, + "components": { + "common": { + "viewMore": "Voir {count} de plus" + }, + "messageInput": { + "placeholder": "Poser une question de suivi" + }, + "messageBox": { + "sources": "Sources", + "answer": "Réponse", + "related": "Connexe" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "Fichiers joints", + "add": "Ajouter", + "clear": "Effacer", + "attach": "Joindre", + "uploading": "Téléversement...", + "files": "{count} fichiers" + }, + "focus": { + "button": "Focus", + "modes": { + "webSearch": { + "title": "Tous", + "description": "Recherche sur tout Internet" + }, + "academicSearch": { + "title": "Académique", + "description": "Recherche dans des articles académiques publiés" + }, + "writingAssistant": { + "title": "Rédaction", + "description": "Discuter sans rechercher sur le web" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "Moteur de connaissance computationnelle" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "Rechercher et regarder des vidéos" + }, + "redditSearch": { + "title": "Reddit", + "description": "Rechercher des discussions et des opinions" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "Vitesse", + "description": "Prioriser la vitesse pour obtenir la réponse la plus rapide" + }, + "balanced": { + "title": "Équilibré", + "description": "Trouver un équilibre entre vitesse et précision" + }, + "quality": { + "title": "Qualité (bientôt)", + "description": "Obtenir la réponse la plus complète et la plus précise" + } + } + }, + "messageActions": { + "rewrite": "Réécrire" + }, + "searchImages": { + "searchButton": "Rechercher des images" + }, + "searchVideos": { + "searchButton": "Rechercher des vidéos", + "badge": "Vidéo" + }, + "weather": { + "humidity": "Humidité", + "now": "Maintenant" + }, + "newsArticleWidget": { + "error": "Impossible de charger les actualités." + }, + "emptyChat": { + "title": "La recherche commence ici." + }, + "emptyChatMessageInput": { + "placeholder": "Demandez-moi n'importe quoi..." + }, + "deleteChat": { + "title": "Confirmation de suppression", + "description": "Êtes-vous sûr de vouloir supprimer cette conversation ?", + "cancel": "Annuler", + "delete": "Supprimer" + }, + "themeSwitcher": { + "options": { + "light": "Clair", + "dark": "Sombre" + } + } + } +} diff --git a/messages/ja.json b/messages/ja.json new file mode 100644 index 0000000..b62f3e9 --- /dev/null +++ b/messages/ja.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - インターネットと会話", + "description": "Perplexica はインターネットに接続された AI チャットボットです。" + }, + "manifest": { + "name": "Perplexica - インターネットと会話", + "shortName": "Perplexica", + "description": "Perplexica はインターネットに接続された AI チャットボットです。" + }, + "navigation": { + "home": "ホーム", + "discover": "ディスカバー", + "library": "ライブラリ", + "settings": "設定" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "書き出し日:", + "citations": "参考文献:", + "user": "ユーザー", + "assistant": "アシスタント", + "errors": { + "noChatModelsAvailable": "利用可能なチャットモデルがありません", + "chatProviderNotConfigured": "チャットモデルのプロバイダーが設定されていません。設定ページまたは設定ファイルで設定してください。", + "noEmbeddingModelsAvailable": "利用可能な埋め込みモデルがありません", + "cannotSendBeforeConfigReady": "設定が完了するまでメッセージを送信できません", + "failedToDeleteChat": "チャットの削除に失敗しました" + } + }, + "navbar": { + "exportAsMarkdown": "Markdown として書き出し", + "exportAsPDF": "PDF として書き出し" + }, + "export": { + "chatExportTitle": "チャット書き出し:{title}" + }, + "weather": { + "conditions": { + "clear": "快晴", + "mainlyClear": "晴れ時々薄曇り", + "partlyCloudy": "所により曇り", + "cloudy": "曇り", + "fog": "霧", + "lightDrizzle": "弱い霧雨", + "moderateDrizzle": "やや強い霧雨", + "denseDrizzle": "強い霧雨", + "lightFreezingDrizzle": "弱い着氷性霧雨", + "denseFreezingDrizzle": "強い着氷性霧雨", + "slightRain": "小雨", + "moderateRain": "雨", + "heavyRain": "大雨", + "lightFreezingRain": "弱い凍雨", + "heavyFreezingRain": "強い凍雨", + "slightSnowFall": "小雪", + "moderateSnowFall": "雪", + "heavySnowFall": "大雪", + "snow": "降雪", + "slightRainShowers": "にわか小雨", + "moderateRainShowers": "にわか雨", + "heavyRainShowers": "激しいにわか雨", + "slightSnowShowers": "にわか小雪", + "moderateSnowShowers": "にわか雪", + "heavySnowShowers": "激しいにわか雪", + "thunderstorm": "雷雨", + "thunderstormSlightHail": "雷雨(小さな雹)", + "thunderstormHeavyHail": "雷雨(大きな雹)" + } + }, + "pages": { + "home": { + "title": "チャット - Perplexica", + "description": "インターネットと会話し、Perplexica と対話しましょう。" + }, + "discover": { + "title": "ディスカバー", + "topics": { + "tech": "テック・サイエンス", + "finance": "ファイナンス", + "art": "アート・文化", + "sports": "スポーツ", + "entertainment": "エンタメ" + }, + "errorFetchingData": "データの取得中にエラーが発生しました" + }, + "library": { + "title": "ライブラリ", + "empty": "チャットが見つかりません。", + "ago": "{time} 前" + }, + "settings": { + "title": "設定", + "sections": { + "preferences": "環境設定", + "automaticSearch": "自動検索", + "systemInstructions": "システム指示", + "modelSettings": "モデル設定", + "apiKeys": "API キー" + }, + "preferences": { + "theme": "テーマ", + "measurementUnits": "単位", + "language": "言語", + "metric": "メートル法", + "imperial": "ヤード・ポンド法" + }, + "automaticSearch": { + "image": { + "title": "自動画像検索", + "desc": "チャットの回答で関連画像を自動検索" + }, + "video": { + "title": "自動画像検索", + "desc": "チャットの回答で関連動画を自動検索" + } + }, + "model": { + "chatProvider": "チャットモデルのプロバイダー", + "chat": "チャットモデル", + "noModels": "利用可能なモデルはありません", + "invalidProvider": "無効なプロバイダーです。バックエンドのログを確認してください", + "custom": { + "modelName": "モデル名", + "apiKey": "カスタム OpenAI API キー", + "baseUrl": "カスタム OpenAI Base URL" + } + }, + "embedding": { + "provider": "埋め込みモデルのプロバイダー", + "model": "埋め込みモデル" + }, + "api": { + "openaiApiKey": "OpenAI API キー", + "ollamaApiUrl": "Ollama API URL", + "groqApiKey": "GROQ API キー", + "anthropicApiKey": "Anthropic API キー", + "geminiApiKey": "Gemini API キー", + "deepseekApiKey": "Deepseek API キー", + "aimlApiKey": "AI/ML API キー", + "lmStudioApiUrl": "LM Studio API URL" + }, + "systemInstructions": { + "placeholder": "LLM への特別な指示があれば入力してください" + } + } + }, + "components": { + "common": { + "viewMore": "さらに {count} 件を表示" + }, + "messageInput": { + "placeholder": "追質問する" + }, + "messageBox": { + "sources": "出典", + "answer": "回答", + "related": "関連" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "添付ファイル", + "add": "追加", + "clear": "クリア", + "attach": "添付", + "uploading": "アップロード中...", + "files": "{count} 件のファイル" + }, + "focus": { + "button": "フォーカス", + "modes": { + "webSearch": { + "title": "すべて", + "description": "インターネット全体を検索" + }, + "academicSearch": { + "title": "学術", + "description": "公開済みの学術論文を検索" + }, + "writingAssistant": { + "title": "ライティング", + "description": "ウェブ検索をせずにチャット" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "計算知識エンジン" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "動画を検索・視聴" + }, + "redditSearch": { + "title": "Reddit", + "description": "議論と意見を検索" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "スピード", + "description": "速度を優先し、最速で回答を取得" + }, + "balanced": { + "title": "バランス", + "description": "速度と正確性のバランスを取る" + }, + "quality": { + "title": "クオリティ(近日対応)", + "description": "最も丁寧で正確な回答を取得" + } + } + }, + "messageActions": { + "rewrite": "書き直す" + }, + "searchImages": { + "searchButton": "画像を検索" + }, + "searchVideos": { + "searchButton": "動画を検索", + "badge": "動画" + }, + "weather": { + "humidity": "湿度", + "now": "現在" + }, + "newsArticleWidget": { + "error": "ニュースを読み込めませんでした。" + }, + "emptyChat": { + "title": "ここからリサーチが始まる。" + }, + "emptyChatMessageInput": { + "placeholder": "何でも聞いてください..." + }, + "deleteChat": { + "title": "削除の確認", + "description": "このチャットを削除してよろしいですか?", + "cancel": "キャンセル", + "delete": "削除" + }, + "themeSwitcher": { + "options": { + "light": "ライト", + "dark": "ダーク" + } + } + } +} diff --git a/messages/ko.json b/messages/ko.json new file mode 100644 index 0000000..c6d9128 --- /dev/null +++ b/messages/ko.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - 인터넷과 대화", + "description": "Perplexica는 인터넷에 연결된 AI 챗봇입니다." + }, + "manifest": { + "name": "Perplexica - 인터넷과 대화", + "shortName": "Perplexica", + "description": "Perplexica는 인터넷에 연결된 AI 챗봇입니다." + }, + "navigation": { + "home": "홈", + "discover": "디스커버", + "library": "라이브러리", + "settings": "설정" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "내보낸 날짜:", + "citations": "참고 출처:", + "user": "사용자", + "assistant": "도우미", + "errors": { + "noChatModelsAvailable": "사용 가능한 채팅 모델이 없습니다", + "chatProviderNotConfigured": "채팅 모델 공급자가 구성되지 않은 것 같습니다. 설정 페이지 또는 구성 파일에서 설정하세요.", + "noEmbeddingModelsAvailable": "사용 가능한 임베딩 모델이 없습니다", + "cannotSendBeforeConfigReady": "구성이 완료되기 전에는 메시지를 보낼 수 없습니다", + "failedToDeleteChat": "채팅 삭제에 실패했습니다" + } + }, + "navbar": { + "exportAsMarkdown": "Markdown으로 내보내기", + "exportAsPDF": "PDF로 내보내기" + }, + "export": { + "chatExportTitle": "채팅 내보내기: {title}" + }, + "weather": { + "conditions": { + "clear": "맑음", + "mainlyClear": "대체로 맑음", + "partlyCloudy": "부분적으로 구름", + "cloudy": "흐림", + "fog": "안개", + "lightDrizzle": "약한 이슬비", + "moderateDrizzle": "보통 이슬비", + "denseDrizzle": "강한 이슬비", + "lightFreezingDrizzle": "약한 착빙성 이슬비", + "denseFreezingDrizzle": "강한 착빙성 이슬비", + "slightRain": "약한 비", + "moderateRain": "보통 비", + "heavyRain": "강한 비", + "lightFreezingRain": "약한 어는 비", + "heavyFreezingRain": "강한 어는 비", + "slightSnowFall": "약한 눈", + "moderateSnowFall": "눈", + "heavySnowFall": "폭설", + "snow": "강설", + "slightRainShowers": "약한 소나기", + "moderateRainShowers": "소나기", + "heavyRainShowers": "강한 소나기", + "slightSnowShowers": "약한 눈보라", + "moderateSnowShowers": "눈보라", + "heavySnowShowers": "강한 눈보라", + "thunderstorm": "뇌우", + "thunderstormSlightHail": "약한 우박을 동반한 뇌우", + "thunderstormHeavyHail": "강한 우박을 동반한 뇌우" + } + }, + "pages": { + "home": { + "title": "채팅 - Perplexica", + "description": "인터넷과 대화하고 Perplexica와 대화하세요." + }, + "discover": { + "title": "디스커버", + "topics": { + "tech": "기술과 과학", + "finance": "금융", + "art": "예술과 문화", + "sports": "스포츠", + "entertainment": "엔터테인먼트" + }, + "errorFetchingData": "데이터를 가져오는 중 오류가 발생했습니다" + }, + "library": { + "title": "라이브러리", + "empty": "채팅을 찾을 수 없습니다.", + "ago": "{time} 전" + }, + "settings": { + "title": "설정", + "sections": { + "preferences": "기본 설정", + "automaticSearch": "자동 검색", + "systemInstructions": "시스템 지시", + "modelSettings": "모델 설정", + "apiKeys": "API 키" + }, + "preferences": { + "theme": "테마", + "measurementUnits": "단위", + "language": "언어", + "metric": "미터법", + "imperial": "야드파운드법" + }, + "automaticSearch": { + "image": { + "title": "자동 이미지 검색", + "desc": "채팅 응답에서 관련 이미지를 자동으로 검색" + }, + "video": { + "title": "자동 비디오 검색", + "desc": "채팅 응답에서 관련 비디오를 자동으로 검색" + } + }, + "model": { + "chatProvider": "채팅 모델 공급자", + "chat": "채팅 모델", + "noModels": "사용 가능한 모델이 없습니다", + "invalidProvider": "잘못된 공급자입니다. 백엔드 로그를 확인하세요", + "custom": { + "modelName": "모델 이름", + "apiKey": "사용자 지정 OpenAI API 키", + "baseUrl": "사용자 지정 OpenAI Base URL" + } + }, + "embedding": { + "provider": "임베딩 모델 공급자", + "model": "임베딩 모델" + }, + "api": { + "openaiApiKey": "OpenAI API 키", + "ollamaApiUrl": "Ollama API URL", + "groqApiKey": "GROQ API 키", + "anthropicApiKey": "Anthropic API 키", + "geminiApiKey": "Gemini API 키", + "deepseekApiKey": "Deepseek API 키", + "aimlApiKey": "AI/ML API 키", + "lmStudioApiUrl": "LM Studio API URL" + }, + "systemInstructions": { + "placeholder": "LLM에 대한 특별한 지시사항" + } + } + }, + "components": { + "common": { + "viewMore": "추가 {count}개 보기" + }, + "messageInput": { + "placeholder": "후속 질문하기" + }, + "messageBox": { + "sources": "출처", + "answer": "답변", + "related": "관련" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "첨부된 파일", + "add": "추가", + "clear": "지우기", + "attach": "첨부", + "uploading": "업로드 중...", + "files": "{count}개 파일" + }, + "focus": { + "button": "포커스", + "modes": { + "webSearch": { + "title": "전체", + "description": "인터넷 전체 검색" + }, + "academicSearch": { + "title": "학술", + "description": "발표된 학술 논문 검색" + }, + "writingAssistant": { + "title": "글쓰기", + "description": "웹을 검색하지 않고 채팅" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "계산 지식 엔진" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "동영상 검색 및 시청" + }, + "redditSearch": { + "title": "Reddit", + "description": "토론과 의견 검색" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "속도", + "description": "속도를 우선하여 가장 빠른 답변 얻기" + }, + "balanced": { + "title": "균형", + "description": "속도와 정확성의 균형 맞추기" + }, + "quality": { + "title": "품질(곧 제공)", + "description": "가장 철저하고 정확한 답변 얻기" + } + } + }, + "messageActions": { + "rewrite": "다시 쓰기" + }, + "searchImages": { + "searchButton": "이미지 검색" + }, + "searchVideos": { + "searchButton": "동영상 검색", + "badge": "동영상" + }, + "weather": { + "humidity": "습도", + "now": "지금" + }, + "newsArticleWidget": { + "error": "뉴스를 불러올 수 없습니다." + }, + "emptyChat": { + "title": "연구는 여기에서 시작됩니다." + }, + "emptyChatMessageInput": { + "placeholder": "아무거나 물어보세요..." + }, + "deleteChat": { + "title": "삭제 확인", + "description": "이 채팅을 삭제하시겠습니까?", + "cancel": "취소", + "delete": "삭제" + }, + "themeSwitcher": { + "options": { + "light": "라이트", + "dark": "다크" + } + } + } +} diff --git a/messages/zh-CN.json b/messages/zh-CN.json new file mode 100644 index 0000000..b2e0f4e --- /dev/null +++ b/messages/zh-CN.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - 与网络对话", + "description": "Perplexica 是一个能连接互联网的 AI 聊天机器人。" + }, + "manifest": { + "name": "Perplexica - 与网络对话", + "shortName": "Perplexica", + "description": "Perplexica 是一个能连接互联网的 AI 聊天机器人。" + }, + "navigation": { + "home": "首页", + "discover": "探索", + "library": "资料库", + "settings": "设置" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "导出日期:", + "citations": "参考来源:", + "user": "使用者", + "assistant": "助理", + "errors": { + "noChatModelsAvailable": "没有可用的聊天模型", + "chatProviderNotConfigured": "看起来你还没有配置任何聊天模型供应商,请到设置页或配置文件进行设置。", + "noEmbeddingModelsAvailable": "没有可用的向量嵌入模型", + "cannotSendBeforeConfigReady": "在设置就绪前无法发送消息", + "failedToDeleteChat": "删除聊天失败" + } + }, + "navbar": { + "exportAsMarkdown": "导出为 Markdown", + "exportAsPDF": "导出为 PDF" + }, + "export": { + "chatExportTitle": "聊天导出:{title}" + }, + "weather": { + "conditions": { + "clear": "晴朗", + "mainlyClear": "大致晴朗", + "partlyCloudy": "局部多云", + "cloudy": "多云", + "fog": "雾", + "lightDrizzle": "小毛毛雨", + "moderateDrizzle": "中等毛毛雨", + "denseDrizzle": "大毛毛雨", + "lightFreezingDrizzle": "轻微冰雾雨", + "denseFreezingDrizzle": "强冰雾雨", + "slightRain": "小雨", + "moderateRain": "中雨", + "heavyRain": "大雨", + "lightFreezingRain": "轻微冻雨", + "heavyFreezingRain": "强冻雨", + "slightSnowFall": "小雪", + "moderateSnowFall": "中雪", + "heavySnowFall": "大雪", + "snow": "降雪", + "slightRainShowers": "短暂小雨", + "moderateRainShowers": "短暂中雨", + "heavyRainShowers": "短暂大雨", + "slightSnowShowers": "短暂小雪", + "moderateSnowShowers": "短暂中雪", + "heavySnowShowers": "短暂大雪", + "thunderstorm": "雷雨", + "thunderstormSlightHail": "雷雨伴随小冰雹", + "thunderstormHeavyHail": "雷雨伴随大冰雹" + } + }, + "pages": { + "home": { + "title": "聊天 - Perplexica", + "description": "连上互联网聊聊天,与 Perplexica 对话。" + }, + "discover": { + "title": "探索", + "topics": { + "tech": "科技与科学", + "finance": "财经", + "art": "艺术与文化", + "sports": "运动", + "entertainment": "娱乐" + }, + "errorFetchingData": "获取数据时发生错误" + }, + "library": { + "title": "资料库", + "empty": "没有找到任何聊天记录。", + "ago": "{time} 前" + }, + "settings": { + "title": "设置", + "sections": { + "preferences": "偏好设置", + "automaticSearch": "自动搜索", + "systemInstructions": "系统指示", + "modelSettings": "模型设置", + "apiKeys": "API 密钥" + }, + "preferences": { + "theme": "主题", + "measurementUnits": "计量单位", + "language": "语言", + "metric": "公制", + "imperial": "英制" + }, + "automaticSearch": { + "image": { + "title": "自动图片搜索", + "desc": "在聊天回复中自动搜索相关图片" + }, + "video": { + "title": "自动视频搜索", + "desc": "在聊天回复中自动搜索相关视频" + } + }, + "model": { + "chatProvider": "聊天模型供应商", + "chat": "聊天模型", + "noModels": "没有可用的模型", + "invalidProvider": "供应商无效,请检查后端日志", + "custom": { + "modelName": "模型名称", + "apiKey": "自定义 OpenAI API 密钥", + "baseUrl": "自定义 OpenAI Base URL" + } + }, + "embedding": { + "provider": "向量嵌入供应商", + "model": "向量嵌入模型" + }, + "api": { + "openaiApiKey": "OpenAI API 密钥", + "ollamaApiUrl": "Ollama API 地址", + "groqApiKey": "GROQ API 密钥", + "anthropicApiKey": "Anthropic API 密钥", + "geminiApiKey": "Gemini API 密钥", + "deepseekApiKey": "Deepseek API 密钥", + "aimlApiKey": "AI/ML 密钥", + "lmStudioApiUrl": "LM Studio API 地址" + }, + "systemInstructions": { + "placeholder": "任何要给 LLM 的特别指示" + } + } + }, + "components": { + "common": { + "viewMore": "查看另外 {count} 项" + }, + "messageInput": { + "placeholder": "提出追问" + }, + "messageBox": { + "sources": "参考来源", + "answer": "回答", + "related": "相关内容" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "已附加的文件", + "add": "新增", + "clear": "清除", + "attach": "附加", + "uploading": "上传中...", + "files": "{count} 个文件" + }, + "focus": { + "button": "焦点", + "modes": { + "webSearch": { + "title": "全部", + "description": "在整个互联网搜索" + }, + "academicSearch": { + "title": "学术", + "description": "搜索已发表的学术论文" + }, + "writingAssistant": { + "title": "写作", + "description": "不搜索网络,直接聊天" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "计算型知识引擎" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "搜索和观看视频" + }, + "redditSearch": { + "title": "Reddit", + "description": "搜索讨论与观点" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "速度", + "description": "优先速度,以最快的方式得到答案。" + }, + "balanced": { + "title": "平衡", + "description": "在速度与准确度之间取得平衡" + }, + "quality": { + "title": "品质(即将推出)", + "description": "取得最完整与最精确的回答" + } + } + }, + "messageActions": { + "rewrite": "重写" + }, + "searchImages": { + "searchButton": "搜索图片" + }, + "searchVideos": { + "searchButton": "搜索视频", + "badge": "视频" + }, + "weather": { + "humidity": "湿度", + "now": "现在" + }, + "newsArticleWidget": { + "error": "无法载入新闻。" + }, + "emptyChat": { + "title": "研究从这里开始。" + }, + "emptyChatMessageInput": { + "placeholder": "问我任何事..." + }, + "deleteChat": { + "title": "删除确认", + "description": "确定要删除此聊天吗?", + "cancel": "取消", + "delete": "删除" + }, + "themeSwitcher": { + "options": { + "light": "浅色", + "dark": "深色" + } + } + } +} diff --git a/messages/zh-HK.json b/messages/zh-HK.json new file mode 100644 index 0000000..7b1ca84 --- /dev/null +++ b/messages/zh-HK.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - 與網絡對話", + "description": "Perplexica 是一個能連接互聯網的 AI 聊天機械人。" + }, + "manifest": { + "name": "Perplexica - 與網絡對話", + "shortName": "Perplexica", + "description": "Perplexica 是一個能連接互聯網的 AI 聊天機械人。" + }, + "navigation": { + "home": "主頁", + "discover": "探索", + "library": "資料庫", + "settings": "設定" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "匯出日期:", + "citations": "參考來源:", + "user": "用戶", + "assistant": "助理", + "errors": { + "noChatModelsAvailable": "沒有可用的聊天模型", + "chatProviderNotConfigured": "你似乎未有設定任何聊天模型供應商,請到設定頁或設定檔進行設定。", + "noEmbeddingModelsAvailable": "沒有可用的向量嵌入模型", + "cannotSendBeforeConfigReady": "在設定完成前無法發送訊息", + "failedToDeleteChat": "刪除聊天失敗" + } + }, + "navbar": { + "exportAsMarkdown": "匯出為 Markdown", + "exportAsPDF": "匯出為 PDF" + }, + "export": { + "chatExportTitle": "聊天匯出:{title}" + }, + "weather": { + "conditions": { + "clear": "天晴", + "mainlyClear": "大致天晴", + "partlyCloudy": "局部多雲", + "cloudy": "多雲", + "fog": "霧", + "lightDrizzle": "微毛毛雨", + "moderateDrizzle": "毛毛雨", + "denseDrizzle": "密集毛毛雨", + "lightFreezingDrizzle": "輕微凍霧雨", + "denseFreezingDrizzle": "強凍霧雨", + "slightRain": "微雨", + "moderateRain": "雨", + "heavyRain": "大雨", + "lightFreezingRain": "輕微凍雨", + "heavyFreezingRain": "強凍雨", + "slightSnowFall": "微雪", + "moderateSnowFall": "下雪", + "heavySnowFall": "大雪", + "snow": "降雪", + "slightRainShowers": "驟雨", + "moderateRainShowers": "驟雨", + "heavyRainShowers": "大驟雨", + "slightSnowShowers": "驟雪", + "moderateSnowShowers": "驟雪", + "heavySnowShowers": "大驟雪", + "thunderstorm": "雷暴", + "thunderstormSlightHail": "雷暴(小冰雹)", + "thunderstormHeavyHail": "雷暴(大冰雹)" + } + }, + "pages": { + "home": { + "title": "聊天 - Perplexica", + "description": "連接網絡傾下計,與 Perplexica 對話。" + }, + "discover": { + "title": "探索", + "topics": { + "tech": "科技與科學", + "finance": "財經", + "art": "藝術與文化", + "sports": "體育", + "entertainment": "娛樂" + }, + "errorFetchingData": "取得資料時發生錯誤" + }, + "library": { + "title": "資料庫", + "empty": "未找到任何聊天紀錄。", + "ago": "{time} 前" + }, + "settings": { + "title": "設定", + "sections": { + "preferences": "偏好設定", + "automaticSearch": "自動搜尋", + "systemInstructions": "系統指示", + "modelSettings": "模型設定", + "apiKeys": "API 金鑰" + }, + "preferences": { + "theme": "主題", + "measurementUnits": "度量單位", + "language": "語言", + "metric": "公制", + "imperial": "英制" + }, + "automaticSearch": { + "image": { + "title": "自動圖片搜尋", + "desc": "在聊天回應中自動搜尋相關圖片" + }, + "video": { + "title": "自動影片搜尋", + "desc": "在聊天回應中自動搜尋相關影片" + } + }, + "model": { + "chatProvider": "聊天模型供應商", + "chat": "聊天模型", + "noModels": "沒有可用的模型", + "invalidProvider": "供應商無效,請檢查後端日誌", + "custom": { + "modelName": "模型名稱", + "apiKey": "自訂 OpenAI API 金鑰", + "baseUrl": "自訂 OpenAI Base URL" + } + }, + "embedding": { + "provider": "向量嵌入供應商", + "model": "向量嵌入模型" + }, + "api": { + "openaiApiKey": "OpenAI API 金鑰", + "ollamaApiUrl": "Ollama API 位址", + "groqApiKey": "GROQ API 金鑰", + "anthropicApiKey": "Anthropic API 金鑰", + "geminiApiKey": "Gemini API 金鑰", + "deepseekApiKey": "Deepseek API 金鑰", + "aimlApiKey": "AI/ML 金鑰", + "lmStudioApiUrl": "LM Studio API 位址" + }, + "systemInstructions": { + "placeholder": "任何要給 LLM 的特別指示" + } + } + }, + "components": { + "common": { + "viewMore": "查看另外 {count} 項" + }, + "messageInput": { + "placeholder": "提出追問" + }, + "messageBox": { + "sources": "參考來源", + "answer": "回答", + "related": "相關內容" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "已附加的檔案", + "add": "新增", + "clear": "清除", + "attach": "附加", + "uploading": "上載中...", + "files": "{count} 個檔案" + }, + "focus": { + "button": "焦點", + "modes": { + "webSearch": { + "title": "全部", + "description": "在整個網絡上搜尋" + }, + "academicSearch": { + "title": "學術", + "description": "搜尋已發表的學術論文" + }, + "writingAssistant": { + "title": "寫作", + "description": "不搜尋網絡,直接聊天" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "計算型知識引擎" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "搜尋及觀看影片" + }, + "redditSearch": { + "title": "Reddit", + "description": "搜尋討論與觀點" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "速度", + "description": "優先速度,以最快的方式得到答案。" + }, + "balanced": { + "title": "平衡", + "description": "在速度與準確度之間取得平衡" + }, + "quality": { + "title": "品質(即將推出)", + "description": "取得最完整與最精確的回答" + } + } + }, + "messageActions": { + "rewrite": "重寫" + }, + "searchImages": { + "searchButton": "搜尋圖片" + }, + "searchVideos": { + "searchButton": "搜尋影片", + "badge": "影片" + }, + "weather": { + "humidity": "濕度", + "now": "現在" + }, + "newsArticleWidget": { + "error": "無法載入新聞。" + }, + "emptyChat": { + "title": "研究由此開始。" + }, + "emptyChatMessageInput": { + "placeholder": "問我任何事..." + }, + "deleteChat": { + "title": "刪除確認", + "description": "確定要刪除此聊天嗎?", + "cancel": "取消", + "delete": "刪除" + }, + "themeSwitcher": { + "options": { + "light": "淺色", + "dark": "深色" + } + } + } +} diff --git a/messages/zh-TW.json b/messages/zh-TW.json new file mode 100644 index 0000000..798111b --- /dev/null +++ b/messages/zh-TW.json @@ -0,0 +1,251 @@ +{ + "metadata": { + "title": "Perplexica - 與網路對話", + "description": "Perplexica 是一個能連上網路的 AI 聊天機器人。" + }, + "manifest": { + "name": "Perplexica - 與網路對話", + "shortName": "Perplexica", + "description": "Perplexica 是一個能連上網路的 AI 聊天機器人。" + }, + "navigation": { + "home": "首頁", + "discover": "探索", + "library": "資料庫", + "settings": "設定" + }, + "common": { + "appName": "Perplexica", + "exportedOn": "匯出日期:", + "citations": "參考來源:", + "user": "使用者", + "assistant": "助理", + "errors": { + "noChatModelsAvailable": "沒有可用的聊天模型", + "chatProviderNotConfigured": "看起來你還沒有設定任何聊天模型供應商,請至設定頁或設定檔進行設定。", + "noEmbeddingModelsAvailable": "沒有可用的向量嵌入模型", + "cannotSendBeforeConfigReady": "在設定就緒前無法送出訊息", + "failedToDeleteChat": "刪除聊天失敗" + } + }, + "navbar": { + "exportAsMarkdown": "匯出為 Markdown", + "exportAsPDF": "匯出為 PDF" + }, + "export": { + "chatExportTitle": "聊天匯出:{title}" + }, + "weather": { + "conditions": { + "clear": "晴朗", + "mainlyClear": "大致晴朗", + "partlyCloudy": "局部多雲", + "cloudy": "多雲", + "fog": "霧", + "lightDrizzle": "小毛毛雨", + "moderateDrizzle": "中等毛毛雨", + "denseDrizzle": "大毛毛雨", + "lightFreezingDrizzle": "輕微冰霧雨", + "denseFreezingDrizzle": "強冰霧雨", + "slightRain": "小雨", + "moderateRain": "中雨", + "heavyRain": "大雨", + "lightFreezingRain": "輕微凍雨", + "heavyFreezingRain": "強凍雨", + "slightSnowFall": "小雪", + "moderateSnowFall": "中雪", + "heavySnowFall": "大雪", + "snow": "降雪", + "slightRainShowers": "短暫小雨", + "moderateRainShowers": "短暫中雨", + "heavyRainShowers": "短暫大雨", + "slightSnowShowers": "短暫小雪", + "moderateSnowShowers": "短暫中雪", + "heavySnowShowers": "短暫大雪", + "thunderstorm": "雷雨", + "thunderstormSlightHail": "雷雨伴隨小冰雹", + "thunderstormHeavyHail": "雷雨伴隨大冰雹" + } + }, + "pages": { + "home": { + "title": "聊天 - Perplexica", + "description": "連上網路聊聊天,和 Perplexica 對話。" + }, + "discover": { + "title": "探索", + "topics": { + "tech": "科技與科學", + "finance": "財經", + "art": "藝術與文化", + "sports": "運動", + "entertainment": "娛樂" + }, + "errorFetchingData": "取得資料時發生錯誤" + }, + "library": { + "title": "資料庫", + "empty": "沒有找到任何聊天紀錄。", + "ago": "{time} 前" + }, + "settings": { + "title": "設定", + "sections": { + "preferences": "偏好設定", + "automaticSearch": "自動搜尋", + "systemInstructions": "系統指示", + "modelSettings": "模型設定", + "apiKeys": "API 金鑰" + }, + "preferences": { + "theme": "主題", + "measurementUnits": "度量單位", + "language": "語言", + "metric": "公制", + "imperial": "英制" + }, + "automaticSearch": { + "image": { + "title": "自動圖片搜尋", + "desc": "在聊天回應中自動搜尋相關圖片" + }, + "video": { + "title": "自動影片搜尋", + "desc": "在聊天回應中自動搜尋相關影片" + } + }, + "model": { + "chatProvider": "聊天模型供應商", + "chat": "聊天模型", + "noModels": "沒有可用的模型", + "invalidProvider": "供應商無效,請檢查後端日誌", + "custom": { + "modelName": "模型名稱", + "apiKey": "自訂 OpenAI API 金鑰", + "baseUrl": "自訂 OpenAI Base URL" + } + }, + "embedding": { + "provider": "向量嵌入供應商", + "model": "向量嵌入模型" + }, + "api": { + "openaiApiKey": "OpenAI API 金鑰", + "ollamaApiUrl": "Ollama API 位址", + "groqApiKey": "GROQ API 金鑰", + "anthropicApiKey": "Anthropic API 金鑰", + "geminiApiKey": "Gemini API 金鑰", + "deepseekApiKey": "Deepseek API 金鑰", + "aimlApiKey": "AI/ML API 金鑰", + "lmStudioApiUrl": "LM Studio API 位址" + }, + "systemInstructions": { + "placeholder": "任何要給 LLM 的特別指示" + } + } + }, + "components": { + "common": { + "viewMore": "查看另外 {count} 項" + }, + "messageInput": { + "placeholder": "提出追問" + }, + "messageBox": { + "sources": "參考來源", + "answer": "回答", + "related": "相關內容" + }, + "copilot": { + "label": "Copilot" + }, + "attach": { + "attachedFiles": "已附加的檔案", + "add": "新增", + "clear": "清除", + "attach": "附加", + "uploading": "上傳中...", + "files": "{count} 個檔案" + }, + "focus": { + "button": "焦點", + "modes": { + "webSearch": { + "title": "全部", + "description": "在整個網路上搜尋" + }, + "academicSearch": { + "title": "學術", + "description": "搜尋已發表的學術論文" + }, + "writingAssistant": { + "title": "寫作", + "description": "不搜尋網路,直接聊天" + }, + "wolframAlphaSearch": { + "title": "Wolfram Alpha", + "description": "計算型知識引擎" + }, + "youtubeSearch": { + "title": "YouTube", + "description": "搜尋與觀看影片" + }, + "redditSearch": { + "title": "Reddit", + "description": "搜尋討論與觀點" + } + } + }, + "optimization": { + "modes": { + "speed": { + "title": "速度", + "description": "優先速度,以最快的方式得到答案。" + }, + "balanced": { + "title": "平衡", + "description": "在速度與準確度之間取得平衡" + }, + "quality": { + "title": "品質(即將推出)", + "description": "取得最完整與最精確的回答" + } + } + }, + "messageActions": { + "rewrite": "重寫" + }, + "searchImages": { + "searchButton": "搜尋圖片" + }, + "searchVideos": { + "searchButton": "搜尋影片", + "badge": "影片" + }, + "weather": { + "humidity": "濕度", + "now": "現在" + }, + "newsArticleWidget": { + "error": "無法載入新聞。" + }, + "emptyChat": { + "title": "研究從這裡開始。" + }, + "emptyChatMessageInput": { + "placeholder": "問我任何事..." + }, + "deleteChat": { + "title": "刪除確認", + "description": "確定要刪除此聊天嗎?", + "cancel": "取消", + "delete": "刪除" + }, + "themeSwitcher": { + "options": { + "light": "淺色", + "dark": "深色" + } + } + } +} diff --git a/next.config.mjs b/next.config.mjs index 2300ff4..1b91594 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,5 @@ +import createNextIntlPlugin from 'next-intl/plugin'; + /** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', @@ -11,4 +13,5 @@ const nextConfig = { serverExternalPackages: ['pdf-parse'], }; -export default nextConfig; +const withNextIntl = createNextIntlPlugin(); +export default withNextIntl(nextConfig); diff --git a/package.json b/package.json index 5715c2a..e35138f 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "mammoth": "^1.9.1", "markdown-to-jsx": "^7.7.2", "next": "^15.2.2", + "next-intl": "^4.3.4", "next-themes": "^0.3.0", "pdf-parse": "^1.1.1", "react": "^18", diff --git a/public/fonts/LICENSE b/public/fonts/LICENSE new file mode 100644 index 0000000..144cabd --- /dev/null +++ b/public/fonts/LICENSE @@ -0,0 +1,36 @@ +Noto Sans Traditional Chinese + +License +Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source' +This Font Software is licensed under the SIL Open Font License, Version 1.1 . This license is copied below, and is also available with a FAQ at: https://openfontlicense.org + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). +"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. +Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. +No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. +The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. +The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. +TERMINATION +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/NotoSansTC-Bold.ttf b/public/fonts/NotoSansTC-Bold.ttf new file mode 100644 index 0000000..46bb6f6 Binary files /dev/null and b/public/fonts/NotoSansTC-Bold.ttf differ diff --git a/public/fonts/NotoSansTC-Regular.ttf b/public/fonts/NotoSansTC-Regular.ttf new file mode 100644 index 0000000..6fd45d3 Binary files /dev/null and b/public/fonts/NotoSansTC-Regular.ttf differ diff --git a/public/fonts/README.md b/public/fonts/README.md new file mode 100644 index 0000000..b8e31cd --- /dev/null +++ b/public/fonts/README.md @@ -0,0 +1,13 @@ +Place font files here for client-side PDF generation. + +Recommended (Noto Sans TC - SIL Open Font License 1.1): + +- NotoSansTC-Regular.ttf +- NotoSansTC-Bold.ttf + +You can download from: + +- https://fonts.google.com/noto/specimen/Noto+Sans+TC +- https://github.com/googlefonts/noto-cjk (Sans/TTF) + +These fonts will be fetched via relative URLs at runtime by jsPDF. diff --git a/src/app/api/discover/route.ts b/src/app/api/discover/route.ts index 415aee8..c5119ea 100644 --- a/src/app/api/discover/route.ts +++ b/src/app/api/discover/route.ts @@ -1,4 +1,5 @@ import { searchSearxng } from '@/lib/searxng'; +import { getLocale } from 'next-intl/server'; const websitesForTopic = { tech: { @@ -37,6 +38,10 @@ export const GET = async (req: Request) => { let data = []; + // derive base language from current locale (e.g., zh-TW -> zh) + const locale = await getLocale(); + const searxLanguage = 'en'; + if (mode === 'normal') { const seenUrls = new Set(); @@ -46,9 +51,9 @@ export const GET = async (req: Request) => { selectedTopic.query.map(async (query) => { return ( await searchSearxng(`site:${link} ${query}`, { - engines: ['bing news'], + engines: ['google news', 'bing news'], pageno: 1, - language: 'en', + language: searxLanguage, }) ).results; }), @@ -68,9 +73,9 @@ export const GET = async (req: Request) => { await searchSearxng( `site:${selectedTopic.links[Math.floor(Math.random() * selectedTopic.links.length)]} ${selectedTopic.query[Math.floor(Math.random() * selectedTopic.query.length)]}`, { - engines: ['bing news'], + engines: ['google news', 'bing news'], pageno: 1, - language: 'en', + language: searxLanguage, }, ) ).results; diff --git a/src/app/api/weather/route.ts b/src/app/api/weather/route.ts index afaf8a6..f1979aa 100644 --- a/src/app/api/weather/route.ts +++ b/src/app/api/weather/route.ts @@ -1,5 +1,8 @@ +import { getTranslations } from 'next-intl/server'; + export const POST = async (req: Request) => { try { + const t = await getTranslations('weather.conditions'); const body: { lat: number; lng: number; @@ -58,104 +61,104 @@ export const POST = async (req: Request) => { switch (code) { case 0: weather.icon = `clear-${dayOrNight}`; - weather.condition = 'Clear'; + weather.condition = t('clear'); break; case 1: - weather.condition = 'Mainly Clear'; + weather.condition = t('mainlyClear'); case 2: - weather.condition = 'Partly Cloudy'; + weather.condition = t('partlyCloudy'); case 3: weather.icon = `cloudy-1-${dayOrNight}`; - weather.condition = 'Cloudy'; + weather.condition = t('cloudy'); break; case 45: - weather.condition = 'Fog'; + weather.condition = t('fog'); case 48: weather.icon = `fog-${dayOrNight}`; - weather.condition = 'Fog'; + weather.condition = t('fog'); break; case 51: - weather.condition = 'Light Drizzle'; + weather.condition = t('lightDrizzle'); case 53: - weather.condition = 'Moderate Drizzle'; + weather.condition = t('moderateDrizzle'); case 55: weather.icon = `rainy-1-${dayOrNight}`; - weather.condition = 'Dense Drizzle'; + weather.condition = t('denseDrizzle'); break; case 56: - weather.condition = 'Light Freezing Drizzle'; + weather.condition = t('lightFreezingDrizzle'); case 57: weather.icon = `frost-${dayOrNight}`; - weather.condition = 'Dense Freezing Drizzle'; + weather.condition = t('denseFreezingDrizzle'); break; case 61: - weather.condition = 'Slight Rain'; + weather.condition = t('slightRain'); case 63: - weather.condition = 'Moderate Rain'; + weather.condition = t('moderateRain'); case 65: - weather.condition = 'Heavy Rain'; + weather.condition = t('heavyRain'); weather.icon = `rainy-2-${dayOrNight}`; break; case 66: - weather.condition = 'Light Freezing Rain'; + weather.condition = t('lightFreezingRain'); case 67: - weather.condition = 'Heavy Freezing Rain'; + weather.condition = t('heavyFreezingRain'); weather.icon = 'rain-and-sleet-mix'; break; case 71: - weather.condition = 'Slight Snow Fall'; + weather.condition = t('slightSnowFall'); case 73: - weather.condition = 'Moderate Snow Fall'; + weather.condition = t('moderateSnowFall'); case 75: - weather.condition = 'Heavy Snow Fall'; + weather.condition = t('heavySnowFall'); weather.icon = `snowy-2-${dayOrNight}`; break; case 77: - weather.condition = 'Snow'; + weather.condition = t('snow'); weather.icon = `snowy-1-${dayOrNight}`; break; case 80: - weather.condition = 'Slight Rain Showers'; + weather.condition = t('slightRainShowers'); case 81: - weather.condition = 'Moderate Rain Showers'; + weather.condition = t('moderateRainShowers'); case 82: - weather.condition = 'Heavy Rain Showers'; + weather.condition = t('heavyRainShowers'); weather.icon = `rainy-3-${dayOrNight}`; break; case 85: - weather.condition = 'Slight Snow Showers'; + weather.condition = t('slightSnowShowers'); case 86: - weather.condition = 'Moderate Snow Showers'; + weather.condition = t('moderateSnowShowers'); case 87: - weather.condition = 'Heavy Snow Showers'; + weather.condition = t('heavySnowShowers'); weather.icon = `snowy-3-${dayOrNight}`; break; case 95: - weather.condition = 'Thunderstorm'; + weather.condition = t('thunderstorm'); weather.icon = `scattered-thunderstorms-${dayOrNight}`; break; case 96: - weather.condition = 'Thunderstorm with Slight Hail'; + weather.condition = t('thunderstormSlightHail'); case 99: - weather.condition = 'Thunderstorm with Heavy Hail'; + weather.condition = t('thunderstormHeavyHail'); weather.icon = 'severe-thunderstorm'; break; default: weather.icon = `clear-${dayOrNight}`; - weather.condition = 'Clear'; + weather.condition = t('clear'); break; } diff --git a/src/app/discover/page.tsx b/src/app/discover/page.tsx index 8e20e50..e0a51ad 100644 --- a/src/app/discover/page.tsx +++ b/src/app/discover/page.tsx @@ -1,8 +1,10 @@ 'use client'; import { Search } from 'lucide-react'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { useTranslations } from 'next-intl'; import Link from 'next/link'; +import Image from 'next/image'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; @@ -13,64 +15,55 @@ interface Discover { thumbnail: string; } -const topics: { key: string; display: string }[] = [ - { - display: 'Tech & Science', - key: 'tech', - }, - { - display: 'Finance', - key: 'finance', - }, - { - display: 'Art & Culture', - key: 'art', - }, - { - display: 'Sports', - key: 'sports', - }, - { - display: 'Entertainment', - key: 'entertainment', - }, +const topics: { + key: 'tech' | 'finance' | 'art' | 'sports' | 'entertainment'; +}[] = [ + { key: 'tech' }, + { key: 'finance' }, + { key: 'art' }, + { key: 'sports' }, + { key: 'entertainment' }, ]; const Page = () => { const [discover, setDiscover] = useState(null); const [loading, setLoading] = useState(true); const [activeTopic, setActiveTopic] = useState(topics[0].key); + const t = useTranslations('pages.discover'); - const fetchArticles = async (topic: string) => { - setLoading(true); - try { - const res = await fetch(`/api/discover?topic=${topic}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); + const fetchArticles = useCallback( + async (topic: string) => { + setLoading(true); + try { + const res = await fetch(`/api/discover?topic=${topic}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); - const data = await res.json(); + const data = await res.json(); - if (!res.ok) { - throw new Error(data.message); + if (!res.ok) { + throw new Error(data.message); + } + + data.blogs = data.blogs.filter((blog: Discover) => blog.thumbnail); + + setDiscover(data.blogs); + } catch (err: any) { + console.error('Error fetching data:', err.message); + toast.error(t('errorFetchingData')); + } finally { + setLoading(false); } - - data.blogs = data.blogs.filter((blog: Discover) => blog.thumbnail); - - setDiscover(data.blogs); - } catch (err: any) { - console.error('Error fetching data:', err.message); - toast.error('Error fetching data'); - } finally { - setLoading(false); - } - }; + }, + [t], + ); useEffect(() => { fetchArticles(activeTopic); - }, [activeTopic]); + }, [activeTopic, fetchArticles]); return ( <> @@ -78,24 +71,24 @@ const Page = () => {
-

Discover

+

{t('title')}


- {topics.map((t, i) => ( + {topics.map((topic, i) => (
setActiveTopic(t.key)} + onClick={() => setActiveTopic(topic.key)} > - {t.display} + {t(`topics.${topic.key}`)}
))}
@@ -129,15 +122,20 @@ const Page = () => { className="max-w-sm rounded-lg overflow-hidden bg-light-secondary dark:bg-dark-secondary hover:-translate-y-[1px] transition duration-200" target="_blank" > - {item.title} +
+ {item.title} +
{item.title.slice(0, 100)}... diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 684a99c..8b885ca 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,10 @@ import { cn } from '@/lib/utils'; import Sidebar from '@/components/Sidebar'; import { Toaster } from 'sonner'; import ThemeProvider from '@/components/theme/Provider'; +import { NextIntlClientProvider } from 'next-intl'; +import { getLocale, getTranslations } from 'next-intl/server'; +import LocaleBootstrap from '@/components/LocaleBootstrap'; +import { LOCALES, DEFAULT_LOCALE, type AppLocale } from '@/i18n/locales'; const montserrat = Montserrat({ weight: ['300', '400', '500', '700'], @@ -13,32 +17,43 @@ const montserrat = Montserrat({ fallback: ['Arial', 'sans-serif'], }); -export const metadata: Metadata = { - title: 'Perplexica - Chat with the internet', - description: - 'Perplexica is an AI powered chatbot that is connected to the internet.', -}; +export async function generateMetadata(): Promise { + const t = await getTranslations('metadata'); + return { + title: t('title'), + description: t('description'), + }; +} -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const locale = await getLocale(); + const appLocale: AppLocale = (LOCALES as readonly string[]).includes( + locale as string, + ) + ? (locale as AppLocale) + : DEFAULT_LOCALE; return ( - + - - {children} - - + + + + {children} + + + ); diff --git a/src/app/library/layout.tsx b/src/app/library/layout.tsx index 00d4a3b..43c5a90 100644 --- a/src/app/library/layout.tsx +++ b/src/app/library/layout.tsx @@ -1,9 +1,13 @@ import { Metadata } from 'next'; import React from 'react'; +import { getTranslations } from 'next-intl/server'; -export const metadata: Metadata = { - title: 'Library - Perplexica', -}; +export async function generateMetadata(): Promise { + const t = await getTranslations('pages.library'); + return { + title: `${t('title')} - Perplexica`, + }; +} const Layout = ({ children }: { children: React.ReactNode }) => { return
{children}
; diff --git a/src/app/library/page.tsx b/src/app/library/page.tsx index 9c40b2b..0f4f6c7 100644 --- a/src/app/library/page.tsx +++ b/src/app/library/page.tsx @@ -1,10 +1,11 @@ 'use client'; import DeleteChat from '@/components/DeleteChat'; -import { cn, formatTimeDifference } from '@/lib/utils'; +import { cn, formatTimeDifference, formatRelativeTime } from '@/lib/utils'; import { BookOpenText, ClockIcon, Delete, ScanEye } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useState } from 'react'; +import { useLocale, useTranslations } from 'next-intl'; export interface Chat { id: string; @@ -16,6 +17,8 @@ export interface Chat { const Page = () => { const [chats, setChats] = useState([]); const [loading, setLoading] = useState(true); + const t = useTranslations('pages.library'); + const locale = useLocale(); useEffect(() => { const fetchChats = async () => { @@ -61,14 +64,14 @@ const Page = () => {
-

Library

+

{t('title')}


{chats.length === 0 && (

- No chats found. + {t('empty')}

)} @@ -94,7 +97,7 @@ const Page = () => {

- {formatTimeDifference(new Date(), chat.createdAt)} Ago + {formatRelativeTime(new Date(), chat.createdAt, locale)}

{ + const t = await getTranslations('manifest'); return { - name: 'Perplexica - Chat with the internet', - short_name: 'Perplexica', - description: - 'Perplexica is an AI powered chatbot that is connected to the internet.', + name: t('name'), + short_name: t('shortName'), + description: t('description'), start_url: '/', display: 'standalone', background_color: '#0a0a0a', diff --git a/src/app/page.tsx b/src/app/page.tsx index e18aca9..df49a07 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,11 +1,15 @@ import ChatWindow from '@/components/ChatWindow'; -import { Metadata } from 'next'; +import type { Metadata } from 'next'; import { Suspense } from 'react'; +import { getTranslations } from 'next-intl/server'; -export const metadata: Metadata = { - title: 'Chat - Perplexica', - description: 'Chat with the internet, chat with Perplexica.', -}; +export async function generateMetadata(): Promise { + const t = await getTranslations('pages.home'); + return { + title: t('title'), + description: t('description'), + }; +} const Home = () => { return ( diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 1b13c9c..69a88d2 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -1,13 +1,16 @@ 'use client'; import { Settings as SettingsIcon, ArrowLeft, Loader2 } from 'lucide-react'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { cn } from '@/lib/utils'; import { Switch } from '@headlessui/react'; import ThemeSwitcher from '@/components/theme/Switcher'; import { ImagesIcon, VideoIcon } from 'lucide-react'; import Link from 'next/link'; import { PROVIDER_METADATA } from '@/lib/providers'; +import LocaleSwitcher from '@/components/LocaleSwitcher'; +import { getPromptLanguageName } from '@/i18n/locales'; +import { useLocale, useTranslations } from 'next-intl'; interface SettingsType { chatModelProviders: { @@ -128,6 +131,8 @@ const SettingsSection = ({ ); const Page = () => { + const t = useTranslations('pages.settings'); + const locale = useLocale(); const [config, setConfig] = useState(null); const [chatModels, setChatModels] = useState>({}); const [embeddingModels, setEmbeddingModels] = useState>( @@ -211,7 +216,8 @@ const Page = () => { localStorage.getItem('autoVideoSearch') === 'true', ); - setSystemInstructions(localStorage.getItem('systemInstructions')!); + const stored = localStorage.getItem('systemInstructions') || ''; + setSystemInstructions(stripPrefixedPrompt(stored)); setMeasureUnit( localStorage.getItem('measureUnit')! as 'Imperial' | 'Metric', @@ -223,6 +229,37 @@ const Page = () => { fetchConfig(); }, []); + // Remove prefix for UI display if it exists in stored value + const stripPrefixedPrompt = (text: string) => { + const trimmed = (text || '').trim(); + const starts = 'Always respond to all non-code content and explanations in'; + if (trimmed.startsWith(starts)) { + const parts = trimmed.split('\n\n'); + // Drop the first block (prefix paragraph and rules) + const rest = parts.slice(1).join('\n\n'); + return rest || ''; + } + return trimmed; + }; + + const buildPrefixedPrompt = useCallback((base: string, loc: string) => { + const langName = getPromptLanguageName(loc); + const prefix = `Always respond to all non-code content and explanations in ${langName}.\nRules:\n1. All descriptions, explanations, and example clarifications must be in ${langName}.\n2. Any content inside code blocks and code comments must be entirely in English.\n3. For language-specific or technical terms, use the original term in that specific language (do not translate it).`; + const trimmed = (base || '').trim(); + // If already starts with the prefix (by simple inclusion of first sentence), avoid duplicating + if ( + trimmed.startsWith( + `Always respond to all non-code content and explanations in`, + ) + ) { + // If locale changed, replace the existing first paragraph block + const parts = trimmed.split('\n\n'); + const rest = parts.slice(1).join('\n\n'); + return `${prefix}${rest ? '\n\n' + rest : ''}`; + } + return prefix + (trimmed ? `\n\n${trimmed}` : ''); + }, []); + const saveConfig = async (key: string, value: any) => { setSavingStates((prev) => ({ ...prev, [key]: true })); @@ -397,7 +434,7 @@ const Page = () => {
-

Settings

+

{t('title')}


@@ -425,16 +462,16 @@ const Page = () => { ) : ( config && (
- +

- Theme + {t('preferences.theme')}

- Measurement Units + {t('preferences.measurementUnits')}