diff --git a/.github/ISSUE_TEMPLATE/NEW-LANGUAGE-REQUEST.yml b/.github/ISSUE_TEMPLATE/NEW-LANGUAGE-REQUEST.yml new file mode 100644 index 000000000..d924019b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/NEW-LANGUAGE-REQUEST.yml @@ -0,0 +1,33 @@ +name: New Language Request +description: Request to add a new language for LibreChat translations. +title: "New Language Request: " +labels: ["enhancement", "i18n"] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to submit a new language request! Please fill out the following details so we can review your request. + - type: input + id: language_name + attributes: + label: Language Name + description: Please provide the full name of the language (e.g., Spanish, Mandarin). + placeholder: e.g., Spanish + validations: + required: true + - type: input + id: iso_code + attributes: + label: ISO 639-1 Code + description: Please provide the ISO 639-1 code for the language (e.g., es for Spanish). You can refer to [this list](https://www.w3schools.com/tags/ref_language_codes.asp) for valid codes. + placeholder: e.g., es + validations: + required: true + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/LibreChat/blob/main/.github/CODE_OF_CONDUCT.md). + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/.github/TRANSLATION.md b/.github/TRANSLATION.md new file mode 100644 index 000000000..080505048 --- /dev/null +++ b/.github/TRANSLATION.md @@ -0,0 +1,70 @@ +# LibreChat Translation Guide + +Thank you for your interest in translating LibreChat! We rely on community contributions to make our application accessible to users around the globe. We manage all translations using [Locize](https://locize.com), a powerful translation management system that integrates seamlessly with our project. + +## How Translations Work + +- **Centralized Management:** All translation strings for LibreChat are managed in a single location on Locize. This allows us to keep translations consistent across all parts of the application. +- **Automatic Updates:** Changes made in Locize are automatically synchronized with our project. You can see the current translation progress for each language via the dynamic badges in our GitHub repository. +- **Community Driven:** We welcome contributions in all languages. Your help ensures that more users can enjoy LibreChat in their native language. + +## Getting Started + +### 1. Create a Locize Account + +If you don't already have an account, please register using our invite link: + +[Register at Locize](https://www.locize.app/register?invitation=t1VDfqoRvj8eUkd1JasxxrBCCI4SAqeeofa2YumAgmVDRxkr4vO1jKqNmpaNCv7H) + +This invitation will give you access to our translation project once you’ve created your account. + + +## Adding a New Language + +If you do not see your language listed in our current translation table, please help us expand our language support by following these steps: + +1. **Create a New Issue:** Open a new issue in the GitHub repository. +2. **Use the Template:** When creating your issue, please select the **New Language Request** template. This template will guide you through providing all the necessary details, including: + - The full name of your language (e.g., Spanish, Mandarin). + - The [ISO 639-1](https://www.w3schools.com/tags/ref_language_codes.asp) code for your language (e.g., es for Spanish). +3. **Collaborate with Maintainers:** Our maintainers will review your issue and work with you to integrate the new language. Once approved, your language will appear in the translation progress table, and you can start contributing translations. + + +## Translation Progress + +Below is our current translation progress for some of the supported languages. Feel free to check these badges and help us improve the translations further: + +| Language | Translation Progress Badge | +|---------------------------------------|----------------------------| +| **English (en)** | ![EN Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'en'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=EN:+) | +| **Arabic (ar)** | ![AR Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'ar'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=AR:+) | +| **German (de)** | ![DE Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'de'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=DE:+) | +| **Spanish (es)** | ![ES Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'es'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=ES:+) | +| **Finnish (fi)** | ![FI Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'fi'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=FI:+) | +| **French (fr)** | ![FR Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'fr'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=FR:+) | +| **Hebrew (he)** | ![HE Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'he'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=HE:+) | +| **Indonesian (id)** | ![ID Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'id'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=ID:+) | +| **Italian (it)** | ![IT Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'it'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=IT:+) | +| **Japanese (ja)** | ![JA Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'ja'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=JA:+) | +| **Korean (ko)** | ![KO Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'ko'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=KO:+) | +| **Dutch (nl)** | ![NL Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'nl'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=NL:+) | +| **Polish (pl)** | ![PL Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'pl'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=PL:+) | +| **Portuguese (pt)** | ![PT Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'pt'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=PT:+) | +| **Russian (ru)** | ![RU Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'ru'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=RU:+) | +| **Swedish (sv)** | ![SV Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'sv'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=SV:+) | +| **Turkish (tr)** | ![TR Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'tr'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=TR:+) | +| **Vietnamese (vi)** | ![VI Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'vi'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=VI:+) | +| **Chinese (Simplified) (zh)** | ![ZH Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'zh'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=ZH:+) | +| **Chinese (Traditional) (zh-Hant)** | ![ZH-HANT Badge](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&color=2096F3&label=Locize&query=%24.versions%5B'latest'%5D.languages%5B'zh-Hant'%5D.translatedPercentage&url=https://api.locize.app/badgedata/4cb2598b-ed4d-469c-9b04-2ed531a8cb45&suffix=%+translated&link=https://www.locize.com&prefix=ZH-HANT:+) | + +--- + +## Need Help? + +If you have any questions about the translation process or need assistance getting started, please feel free to: + +- Open an issue in this repository. +- Join our [Discord community](https://discord.librechat.ai) to chat with fellow translators and contributors. +- Contact one of the project maintainers directly. + +Your contributions help make LibreChat better for users worldwide. Happy translating! diff --git a/.github/workflows/locize-pull-published-sync-pr.yml b/.github/workflows/locize-pull-published-sync-pr.yml new file mode 100644 index 000000000..703d0ccd9 --- /dev/null +++ b/.github/workflows/locize-pull-published-sync-pr.yml @@ -0,0 +1,53 @@ +name: Locize Version Published Workflow + +on: + push: + branches: [main] + repository_dispatch: + types: [locize/versionPublished] + +jobs: + locize_update: + name: Process Locize Version Published Event + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Display Dispatch Payload + run: | + echo "Received Version: ${{ github.event.client_payload.payload.version }}" + echo "Full Payload: ${{ toJson(github.event.client_payload) }}" + - name: Download Translations from locize + uses: locize/download@v1 + with: + project-id: ${{ secrets.LOCIZE_PROJECT_ID }} + version: ${{ github.event.client_payload.payload.version }} + path: "client/src/locales" + path-mask: "{{language}}/{{namespace}}" + clean: true # Ensures old translation files are removed before downloading new ones + + - name: Create Pull Request + id: create_pr + uses: peter-evans/create-pull-request@v7.0.6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: update translations from locize" + branch: locize-update-${{ github.event.client_payload.payload.version }} + delete-branch: true + title: "chore: update translations from locize" + body: | + This PR updates translations from locize. + - Version: `${{ github.event.client_payload.payload.version }}` + - Automated update from GitHub Actions + labels: "translations, automated" + draft: false + + - name: Check PR Outputs + if: steps.create_pr.outputs.pull-request-number + run: | + echo "Pull Request Number - ${{ steps.create_pr.outputs.pull-request-number }}" + echo "Pull Request URL - ${{ steps.create_pr.outputs.pull-request-url }}" \ No newline at end of file diff --git a/.github/workflows/locize-push-missing-keys.yml b/.github/workflows/locize-push-missing-keys.yml new file mode 100644 index 000000000..1244d0c0b --- /dev/null +++ b/.github/workflows/locize-push-missing-keys.yml @@ -0,0 +1,26 @@ +name: Push New Keys to locize + +on: + push: + branches: [main] + +jobs: + push-new-keys: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set Up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install locize CLI + run: npm install -g locize-cli + + - name: Push Missing Translation Keys to locize + run: | + cd client/src/locales + locize save-missing --api-key ${{ secrets.LOCIZE_API_KEY }} --project-id ${{ secrets.LOCIZE_PROJECT_ID }} --language en \ No newline at end of file diff --git a/README.md b/README.md index 317a7a066..0a04518c4 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,15 @@

+

+ + Translation Progress + +

+ + # ✨ Features - 🖥️ **UI & Experience** inspired by ChatGPT with enhanced design and features @@ -79,6 +88,9 @@ - English, 中文, Deutsch, Español, Français, Italiano, Polski, Português Brasileiro - Русский, 日本語, Svenska, 한국어, Tiếng Việt, 繁體中文, العربية, Türkçe, Nederlands, עברית +- 🧠 **Reasoning UI**: + - Dynamic Reasoning UI for Chain-of-Thought/Reasoning AI models like DeepSeek-R1 + - 🎨 **Customizable Interface**: - Customizable Dropdown & Interface that adapts to both power users and newcomers @@ -167,6 +179,8 @@ Contributions, suggestions, bug reports and fixes are welcome! For new features, components, or extensions, please open an issue and discuss before sending a PR. +If you'd like to help translate LibreChat into your language, we'd love your contribution! Improving our translations not only makes LibreChat more accessible to users around the world but also enhances the overall user experience. Please check out our [Translation Guide](.github/TRANSLATION.md). + --- ## 💖 This project exists in its current state thanks to all the people who contribute @@ -174,3 +188,15 @@ For new features, components, or extensions, please open an issue and discuss be + +--- + +## 🎉 Special Thanks + +We thank [Locize](https://locize.com) for their translation management tools that support multiple languages in LibreChat. + +

+ + Locize Logo + +

\ No newline at end of file diff --git a/api/server/services/Endpoints/assistants/build.js b/api/server/services/Endpoints/assistants/build.js index b5eb03a94..544567dd0 100644 --- a/api/server/services/Endpoints/assistants/build.js +++ b/api/server/services/Endpoints/assistants/build.js @@ -3,7 +3,7 @@ const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts'); const { getAssistant } = require('~/models/Assistant'); const buildOptions = async (endpoint, parsedBody) => { - // eslint-disable-next-line no-unused-vars + const { promptPrefix, assistant_id, iconURL, greeting, spec, artifacts, ...modelOptions } = parsedBody; const endpointOption = removeNullishValues({ diff --git a/api/server/services/Endpoints/azureAssistants/build.js b/api/server/services/Endpoints/azureAssistants/build.js index 3785014ca..54a32e4d3 100644 --- a/api/server/services/Endpoints/azureAssistants/build.js +++ b/api/server/services/Endpoints/azureAssistants/build.js @@ -3,7 +3,7 @@ const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts'); const { getAssistant } = require('~/models/Assistant'); const buildOptions = async (endpoint, parsedBody) => { - // eslint-disable-next-line no-unused-vars + const { promptPrefix, assistant_id, iconURL, greeting, spec, artifacts, ...modelOptions } = parsedBody; const endpointOption = removeNullishValues({ diff --git a/client/package.json b/client/package.json index 057b15045..fdfba8072 100644 --- a/client/package.json +++ b/client/package.json @@ -91,6 +91,8 @@ "react-textarea-autosize": "^8.4.0", "react-transition-group": "^4.4.5", "react-virtualized": "^9.22.6", + "react-i18next": "^15.4.0", + "i18next": "^24.2.2", "recoil": "^0.7.7", "regenerator-runtime": "^0.14.1", "rehype-highlight": "^6.0.0", diff --git a/client/src/common/types.ts b/client/src/common/types.ts index b296dcc09..151314faa 100644 --- a/client/src/common/types.ts +++ b/client/src/common/types.ts @@ -6,6 +6,7 @@ import type { SetterOrUpdater } from 'recoil'; import type * as t from 'librechat-data-provider'; import type { UseMutationResult } from '@tanstack/react-query'; import type { LucideIcon } from 'lucide-react'; +import type { TranslationKeys } from '~/hooks'; export type CodeBarProps = { lang: string; @@ -66,7 +67,10 @@ export type GenericSetter = (value: T | ((currentValue: T) => T)) => void; export type LastSelectedModels = Record; -export type LocalizeFunction = (phraseKey: string, ...values: string[]) => string; +export type LocalizeFunction = ( + phraseKey: TranslationKeys, + options?: Record +) => string; export type ChatFormValues = { text: string }; diff --git a/client/src/components/Auth/AuthLayout.tsx b/client/src/components/Auth/AuthLayout.tsx index 6df73d2cf..a7e890517 100644 --- a/client/src/components/Auth/AuthLayout.tsx +++ b/client/src/components/Auth/AuthLayout.tsx @@ -1,4 +1,4 @@ -import { useLocalize } from '~/hooks'; +import { TranslationKeys, useLocalize } from '~/hooks'; import { BlinkAnimation } from './BlinkAnimation'; import { TStartupConfig } from 'librechat-data-provider'; import SocialLoginRender from './SocialLoginRender'; @@ -33,7 +33,7 @@ function AuthLayout({ startupConfig: TStartupConfig | null | undefined; startupConfigError: unknown | null | undefined; pathname: string; - error: string | null; + error: TranslationKeys | null; }) { const localize = useLocalize(); @@ -65,7 +65,7 @@ function AuthLayout({ {localize('com_ui_logo', diff --git a/client/src/components/Auth/Registration.tsx b/client/src/components/Auth/Registration.tsx index 4ae4e03b7..047808ffe 100644 --- a/client/src/components/Auth/Registration.tsx +++ b/client/src/components/Auth/Registration.tsx @@ -6,7 +6,7 @@ import type { TRegisterUser, TError } from 'librechat-data-provider'; import type { TLoginLayoutContext } from '~/common'; import { ErrorMessage } from './ErrorMessage'; import { Spinner } from '~/components/svg'; -import { useLocalize } from '~/hooks'; +import { useLocalize, TranslationKeys } from '~/hooks'; const Registration: React.FC = () => { const navigate = useNavigate(); @@ -56,7 +56,7 @@ const Registration: React.FC = () => { }, }); - const renderInput = (id: string, label: string, type: string, validation: object) => ( + const renderInput = (id: string, label: TranslationKeys, type: string, validation: object) => (
{ : 'com_auth_registration_success_insecure', ) + ' ' + - localize('com_auth_email_verification_redirecting', countdown.toString())} + localize('com_auth_email_verification_redirecting', { 0: countdown.toString() })}
)} {!startupConfigError && !isFetching && ( diff --git a/client/src/components/Auth/VerifyEmail.tsx b/client/src/components/Auth/VerifyEmail.tsx index 0af2f4195..9c143224b 100644 --- a/client/src/components/Auth/VerifyEmail.tsx +++ b/client/src/components/Auth/VerifyEmail.tsx @@ -84,7 +84,7 @@ function RequestPasswordReset() { {countdown > 0 && (

- {localize('com_auth_email_verification_redirecting', countdown.toString())} + {localize('com_auth_email_verification_redirecting', { 0: countdown.toString() })}

)} {showResendLink && countdown === 0 && ( diff --git a/client/src/components/Chat/Input/Files/Table/DataTable.tsx b/client/src/components/Chat/Input/Files/Table/DataTable.tsx index 6b9800b47..31e7296af 100644 --- a/client/src/components/Chat/Input/Files/Table/DataTable.tsx +++ b/client/src/components/Chat/Input/Files/Table/DataTable.tsx @@ -220,8 +220,10 @@ export default function DataTable({ columns, data }: DataTablePro {localize( 'com_files_number_selected', - `${table.getFilteredSelectedRowModel().rows.length}`, - `${table.getFilteredRowModel().rows.length}`, + { + 0: `${table.getFilteredSelectedRowModel().rows.length}`, + 1: `${table.getFilteredRowModel().rows.length}`, + }, )} diff --git a/client/src/components/Chat/Input/Files/Table/SortFilterHeader.tsx b/client/src/components/Chat/Input/Files/Table/SortFilterHeader.tsx index 5bccd2c6a..1b9a0cbe4 100644 --- a/client/src/components/Chat/Input/Files/Table/SortFilterHeader.tsx +++ b/client/src/components/Chat/Input/Files/Table/SortFilterHeader.tsx @@ -9,7 +9,7 @@ import { DropdownMenuTrigger, } from '~/components/ui/DropdownMenu'; import { Button } from '~/components/ui/Button'; -import useLocalize from '~/hooks/useLocalize'; +import { useLocalize, TranslationKeys } from '~/hooks'; import { cn } from '~/utils'; interface SortFilterHeaderProps extends React.HTMLAttributes { @@ -78,9 +78,12 @@ export function SortFilterHeader({ {filters && Object.entries(filters).map(([key, values]) => - values.map((value: string | number) => { - const localizedValue = localize(valueMap?.[value] ?? ''); - const filterValue = localizedValue.length ? localizedValue : valueMap?.[value]; + values.map((value?: string | number) => { + const translationKey = valueMap?.[value ?? '']; + const filterValue = + translationKey != null && translationKey.length + ? localize(translationKey as TranslationKeys) + : String(value); if (!filterValue) { return null; } diff --git a/client/src/components/Chat/Input/PopoverButtons.tsx b/client/src/components/Chat/Input/PopoverButtons.tsx index 3b5279944..2143a870e 100644 --- a/client/src/components/Chat/Input/PopoverButtons.tsx +++ b/client/src/components/Chat/Input/PopoverButtons.tsx @@ -44,8 +44,8 @@ export default function PopoverButtons({ const endpoint = overrideEndpoint ?? endpointType ?? _endpoint ?? ''; const model = overrideModel ?? _model; - const isGenerativeModel = model?.toLowerCase()?.includes('gemini') ?? false; - const isChatModel = (!isGenerativeModel && model?.toLowerCase()?.includes('chat')) ?? false; + const isGenerativeModel = model?.toLowerCase().includes('gemini') ?? false; + const isChatModel = (!isGenerativeModel && model?.toLowerCase().includes('chat')) ?? false; const isTextModel = !isGenerativeModel && !isChatModel && /code|text/.test(model ?? ''); const { showExamples } = optionSettings; diff --git a/client/src/components/Chat/Messages/Content/Image.tsx b/client/src/components/Chat/Messages/Content/Image.tsx index 18bafa49e..28910d031 100644 --- a/client/src/components/Chat/Messages/Content/Image.tsx +++ b/client/src/components/Chat/Messages/Content/Image.tsx @@ -9,12 +9,12 @@ const scaleImage = ({ originalHeight, containerRef, }: { - originalWidth: number; - originalHeight: number; + originalWidth?: number; + originalHeight?: number; containerRef: React.RefObject; }) => { const containerWidth = containerRef.current?.offsetWidth ?? 0; - if (containerWidth === 0 || originalWidth === undefined || originalHeight === undefined) { + if (containerWidth === 0 || originalWidth == null || originalHeight == null) { return { width: 'auto', height: 'auto' }; } const aspectRatio = originalWidth / originalHeight; @@ -35,8 +35,8 @@ const Image = ({ height: number; width: number; placeholderDimensions?: { - height: string; - width: string; + height?: string; + width?: string; }; }) => { const [isLoaded, setIsLoaded] = useState(false); @@ -47,8 +47,8 @@ const Image = ({ const { width: scaledWidth, height: scaledHeight } = useMemo( () => scaleImage({ - originalWidth: Number(placeholderDimensions?.width?.split('px')[0]) ?? width, - originalHeight: Number(placeholderDimensions?.height?.split('px')[0]) ?? height, + originalWidth: Number(placeholderDimensions?.width?.split('px')[0] ?? width), + originalHeight: Number(placeholderDimensions?.height?.split('px')[0] ?? height), containerRef, }), [placeholderDimensions, height, width], diff --git a/client/src/components/Chat/Messages/Content/Parts/LogContent.tsx b/client/src/components/Chat/Messages/Content/Parts/LogContent.tsx index 9a6e3fc99..0d53fb50e 100644 --- a/client/src/components/Chat/Messages/Content/Parts/LogContent.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/LogContent.tsx @@ -64,7 +64,7 @@ const LogContent: React.FC = ({ output = '', renderImages, atta } // const expirationText = expiresAt - // ? ` ${localize('com_download_expires', format(expiresAt, 'MM/dd/yy HH:mm'))}` + // ? ` ${localize('com_download_expires', { 0: format(expiresAt, 'MM/dd/yy HH:mm') })}` // : ` ${localize('com_click_to_download')}`; return ( diff --git a/client/src/components/Chat/Messages/Content/ToolCall.tsx b/client/src/components/Chat/Messages/Content/ToolCall.tsx index 76e39a21b..b8ca8e0a6 100644 --- a/client/src/components/Chat/Messages/Content/ToolCall.tsx +++ b/client/src/components/Chat/Messages/Content/ToolCall.tsx @@ -106,12 +106,12 @@ export default function ToolCall({ const getFinishedText = () => { if (isMCPToolCall === true) { - return localize('com_assistants_completed_function', function_name); + return localize('com_assistants_completed_function', { 0: function_name }); } if (domain != null && domain && domain.length !== Constants.ENCODED_DOMAIN_LENGTH) { - return localize('com_assistants_completed_action', domain); + return localize('com_assistants_completed_action', { 0: domain }); } - return localize('com_assistants_completed_function', function_name); + return localize('com_assistants_completed_function', { 0: function_name }); }; return ( diff --git a/client/src/components/Chat/Messages/Content/ToolPopover.tsx b/client/src/components/Chat/Messages/Content/ToolPopover.tsx index 97e5aefa4..d3a390438 100644 --- a/client/src/components/Chat/Messages/Content/ToolPopover.tsx +++ b/client/src/components/Chat/Messages/Content/ToolPopover.tsx @@ -34,8 +34,8 @@ export default function ToolPopover({
{domain != null && domain - ? localize('com_assistants_domain_info', domain) - : localize('com_assistants_function_use', function_name)} + ? localize('com_assistants_domain_info', { 0: domain }) + : localize('com_assistants_function_use', { 0: function_name })}
diff --git a/client/src/components/Chat/PromptCard.tsx b/client/src/components/Chat/PromptCard.tsx index e348b24d2..4a37fd998 100644 --- a/client/src/components/Chat/PromptCard.tsx +++ b/client/src/components/Chat/PromptCard.tsx @@ -1,14 +1,14 @@ import { TPromptGroup } from 'librechat-data-provider'; import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon'; -export default function PromptCard({ promptGroup }: { promptGroup: TPromptGroup }) { +export default function PromptCard({ promptGroup }: { promptGroup?: TPromptGroup }) { return (
- +

- {promptGroup?.oneliner || promptGroup?.productionPrompt?.prompt} + {(promptGroup?.oneliner ?? '') || promptGroup?.productionPrompt?.prompt}

); diff --git a/client/src/components/Conversations/Conversations.tsx b/client/src/components/Conversations/Conversations.tsx index 941dd848d..67d79c704 100644 --- a/client/src/components/Conversations/Conversations.tsx +++ b/client/src/components/Conversations/Conversations.tsx @@ -1,8 +1,8 @@ import { useMemo, memo } from 'react'; import { parseISO, isToday } from 'date-fns'; import { TConversation } from 'librechat-data-provider'; +import { useLocalize, TranslationKeys } from '~/hooks'; import { groupConversationsByDate } from '~/utils'; -import { useLocalize } from '~/hooks'; import Convo from './Convo'; const Conversations = ({ @@ -41,8 +41,7 @@ const Conversations = ({ paddingLeft: '10px', }} > - {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions */} - {localize(groupName) || groupName} + {localize(groupName as TranslationKeys) || groupName}
{convos.map((convo, i) => (
- {localize(activeSetting)} + {localize(activeSetting as TranslationKeys)} @@ -216,7 +216,9 @@ export default function Fork({ {localize('com_ui_fork_info_1')} {localize('com_ui_fork_info_2')} - {localize('com_ui_fork_info_3', localize('com_ui_fork_split_target'))} + {localize('com_ui_fork_info_3', { + 0: localize('com_ui_fork_split_target'), + })}
@@ -233,7 +235,7 @@ export default function Fork({ hoverTitle={ <> - {localize(optionLabels[ForkOptions.DIRECT_PATH])} + {localize(optionLabels[ForkOptions.DIRECT_PATH] as TranslationKeys)} } hoverDescription={localize('com_ui_fork_info_visible')} @@ -251,7 +253,7 @@ export default function Fork({ hoverTitle={ <> - {localize(optionLabels[ForkOptions.INCLUDE_BRANCHES])} + {localize(optionLabels[ForkOptions.INCLUDE_BRANCHES] as TranslationKeys)} } hoverDescription={localize('com_ui_fork_info_branches')} @@ -269,9 +271,9 @@ export default function Fork({ hoverTitle={ <> - {`${localize(optionLabels[ForkOptions.TARGET_LEVEL])} (${localize( - 'com_endpoint_default', - )})`} + {`${localize( + optionLabels[ForkOptions.TARGET_LEVEL] as TranslationKeys, + )} (${localize('com_endpoint_default')})`} } hoverDescription={localize('com_ui_fork_info_target')} diff --git a/client/src/components/Endpoints/Settings/Advanced.tsx b/client/src/components/Endpoints/Settings/Advanced.tsx index 976e0bd94..601e9a675 100644 --- a/client/src/components/Endpoints/Settings/Advanced.tsx +++ b/client/src/components/Endpoints/Settings/Advanced.tsx @@ -121,7 +121,7 @@ export default function Settings({ Object.values(assistantListMap?.[endpoint ?? ''] ?? {}) as Assistant[], + () => Object.values(assistantListMap[endpoint ?? ''] ?? {}) as Assistant[], [assistantListMap, endpoint], ); const assistants = useMemo(() => { - const currentAssistants = (currentList ?? []).map(({ id, name }) => ({ + const currentAssistants = currentList.map(({ id, name }) => ({ label: name, value: id, })); @@ -52,8 +52,8 @@ export default function Settings({ conversation, setOption, models, readonly }: }); const activeAssistant = useMemo(() => { - if (assistant_id) { - return assistantListMap[endpoint ?? '']?.[assistant_id]; + if (assistant_id != null && assistant_id) { + return assistantListMap[endpoint ?? '']?.[assistant_id] as Assistant | null; } return null; @@ -70,11 +70,13 @@ export default function Settings({ conversation, setOption, models, readonly }: }, [models, activeAssistant, localize]); const [assistantValue, setAssistantValue] = useState