diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 112b3b5c..c6f5d67f 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -67,7 +67,7 @@ const cn = { ConfirmClearAll: "确认清除所有数据?", }, Lang: { - Name: "Language", + Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language` All: "所有语言", Options: { cn: "简体中文", @@ -79,6 +79,7 @@ const cn = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "头像", diff --git a/app/locales/de.ts b/app/locales/de.ts index 56202722..7c0b94db 100644 --- a/app/locales/de.ts +++ b/app/locales/de.ts @@ -71,7 +71,7 @@ const de: LocaleType = { }, Lang: { Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language` - All: "All Languages", + All: "Alle Sprachen", Options: { cn: "简体中文", en: "English", @@ -82,6 +82,7 @@ const de: LocaleType = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "Avatar", diff --git a/app/locales/en.ts b/app/locales/en.ts index cb97c51c..ba648257 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -81,6 +81,7 @@ const en: LocaleType = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "Avatar", diff --git a/app/locales/es.ts b/app/locales/es.ts index df28075e..9997752e 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -69,11 +69,11 @@ const es: LocaleType = { ConfirmClearAll: "Are you sure you want to reset all chat?", }, Lang: { - Name: "Language", - All: "All Languages", + Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language` + All: "Todos los idiomas", Options: { cn: "简体中文", - en: "Inglés", + en: "English", tw: "繁體中文", es: "Español", it: "Italiano", @@ -81,6 +81,7 @@ const es: LocaleType = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "Avatar", diff --git a/app/locales/index.ts b/app/locales/index.ts index dee6f795..757c1719 100644 --- a/app/locales/index.ts +++ b/app/locales/index.ts @@ -7,6 +7,7 @@ import TR from "./tr"; import JP from "./jp"; import DE from "./de"; import VI from "./vi"; +import RU from "./ru"; export type { LocaleType } from "./cn"; @@ -20,6 +21,7 @@ export const AllLangs = [ "jp", "de", "vi", + "ru", ] as const; export type Lang = (typeof AllLangs)[number]; @@ -82,4 +84,5 @@ export default { jp: JP, de: DE, vi: VI, + ru: RU, }[getLang()] as typeof CN; diff --git a/app/locales/it.ts b/app/locales/it.ts index abf655f0..ddb85c94 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -69,8 +69,8 @@ const it: LocaleType = { ConfirmClearAll: "Sei sicuro vuoi cancellare tutte le chat?", }, Lang: { - Name: "Lingue", - All: "All Languages", + Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language` + All: "Tutte le lingue", Options: { cn: "简体中文", en: "English", @@ -81,6 +81,7 @@ const it: LocaleType = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "Avatar", diff --git a/app/locales/jp.ts b/app/locales/jp.ts index de03f9fd..d34ee68a 100644 --- a/app/locales/jp.ts +++ b/app/locales/jp.ts @@ -69,7 +69,7 @@ const jp: LocaleType = { ConfirmClearAll: "すべてのチャットをリセットしてもよろしいですか?", }, Lang: { - Name: "Language", + Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language` All: "所有语言", Options: { cn: "简体中文", @@ -81,6 +81,7 @@ const jp: LocaleType = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "アバター", diff --git a/app/locales/ru.ts b/app/locales/ru.ts new file mode 100644 index 00000000..dc0b149b --- /dev/null +++ b/app/locales/ru.ts @@ -0,0 +1,244 @@ +import { SubmitKey } from "../store/config"; +import type { LocaleType } from "./index"; + +const ru: LocaleType = { + WIP: "Скоро...", + Error: { + Unauthorized: + "Несанкционированный доступ. Пожалуйста, введите код доступа на странице настроек.", + }, + ChatItem: { + ChatItemCount: (count: number) => `${count} сообщений`, + }, + Chat: { + SubTitle: (count: number) => `${count} сообщений с ChatGPT`, + Actions: { + ChatList: "Перейти к списку чатов", + CompressedHistory: "Сжатая история памяти", + Export: "Экспортировать все сообщения в формате Markdown", + Copy: "Копировать", + Stop: "Остановить", + Retry: "Повторить", + Delete: "Удалить", + }, + Rename: "Переименовать чат", + Typing: "Печатает…", + Input: (submitKey: string) => { + var inputHints = `${submitKey} для отправки сообщения`; + if (submitKey === String(SubmitKey.Enter)) { + inputHints += ", Shift + Enter для переноса строки"; + } + return inputHints + ", / для поиска подсказок"; + }, + Send: "Отправить", + Config: { + Reset: "Сбросить настройки", + SaveAs: "Сохранить как маску", + }, + }, + Export: { + Title: "Все сообщения", + Copy: "Копировать все", + Download: "Скачать", + MessageFromYou: "Сообщение от вас", + MessageFromChatGPT: "Сообщение от ChatGPT", + }, + Memory: { + Title: "Память", + EmptyContent: "Пусто.", + Send: "Отправить память", + Copy: "Копировать память", + Reset: "Сбросить сессию", + ResetConfirm: + "При сбросе текущая история переписки и историческая память будут удалены. Вы уверены, что хотите сбросить?", + }, + Home: { + NewChat: "Новый чат", + DeleteChat: "Вы действительно хотите удалить выбранный разговор?", + DeleteToast: "Чат удален", + Revert: "Отмена", + }, + Settings: { + Title: "Настройки", + SubTitle: "Все настройки", + Actions: { + ClearAll: "Очистить все данные", + ResetAll: "Сбросить все настройки", + Close: "Закрыть", + ConfirmResetAll: "Вы уверены, что хотите сбросить все настройки?", + ConfirmClearAll: "Вы уверены, что хотите очистить все данные?", + }, + Lang: { + Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language` + All: "Все языки", + Options: { + cn: "简体中文", + en: "English", + tw: "繁體中文", + es: "Español", + it: "Italiano", + tr: "Türkçe", + jp: "日本語", + de: "Deutsch", + vi: "Vietnamese", + ru: "Русский", + }, + }, + Avatar: "Аватар", + FontSize: { + Title: "Размер шрифта", + SubTitle: "Настроить размер шрифта контента чата", + }, + Update: { + Version: (x: string) => `Версия: ${x}`, + IsLatest: "Последняя версия", + CheckUpdate: "Проверить обновление", + IsChecking: "Проверка обновления...", + FoundUpdate: (x: string) => `Найдена новая версия: ${x}`, + GoToUpdate: "Обновить", + }, + SendKey: "Клавиша отправки", + Theme: "Тема", + TightBorder: "Узкая граница", + SendPreviewBubble: { + Title: "Отправить предпросмотр", + SubTitle: "Предварительный просмотр markdown в пузыре", + }, + Mask: { + Title: "Экран заставки маски", + SubTitle: "Показывать экран заставки маски перед началом нового чата", + }, + Prompt: { + Disable: { + Title: "Отключить автозаполнение", + SubTitle: "Ввод / для запуска автозаполнения", + }, + List: "Список подсказок", + ListCount: (builtin: number, custom: number) => + `${builtin} встроенных, ${custom} пользовательских`, + Edit: "Редактировать", + Modal: { + Title: "Список подсказок", + Add: "Добавить", + Search: "Поиск подсказок", + }, + EditModal: { + Title: "Редактировать подсказку", + }, + }, + HistoryCount: { + Title: "Количество прикрепляемых сообщений", + SubTitle: "Количество отправляемых сообщений, прикрепляемых к каждому запросу", + }, + CompressThreshold: { + Title: "Порог сжатия истории", + SubTitle: + "Будет сжимать, если длина несжатых сообщений превышает указанное значение", + }, + Token: { + Title: "API ключ", + SubTitle: "Используйте свой ключ, чтобы игнорировать лимит доступа", + Placeholder: "API ключ OpenAI", + }, + Usage: { + Title: "Баланс аккаунта", + SubTitle(used: any, total: any) { + return `Использовано в этом месяце $${used}, подписка $${total}`; + }, + IsChecking: "Проверка...", + Check: "Проверить", + NoAccess: "Введите API ключ, чтобы проверить баланс", + }, + AccessCode: { + Title: "Код доступа", + SubTitle: "Контроль доступа включен", + Placeholder: "Требуется код доступа", + }, + Model: "Модель", + Temperature: { + Title: "Температура", + SubTitle: "Чем выше значение, тем более случайный вывод", + }, + MaxTokens: { + Title: "Максимальное количество токенов", + SubTitle: "Максимальная длина вводных и генерируемых токенов", + }, + PresencePenlty: { + Title: "Штраф за повторения", + SubTitle: + "Чем выше значение, тем больше вероятность общения на новые темы", + }, + }, + Store: { + DefaultTopic: "Новый разговор", + BotHello: "Здравствуйте! Как я могу вам помочь сегодня?", + Error: "Что-то пошло не так. Пожалуйста, попробуйте еще раз позже.", + Prompt: { + History: (content: string) => + "Это краткое содержание истории чата между ИИ и пользователем: " + + content, + Topic: + "Пожалуйста, создайте заголовок из четырех или пяти слов, который кратко описывает нашу беседу, без введения, знаков пунктуации, кавычек, точек, символов или дополнительного текста. Удалите кавычки.", + Summarize: + "Кратко изложите нашу дискуссию в 200 словах или менее для использования в будущем контексте.", + }, + }, + Copy: { + Success: "Скопировано в буфер обмена", + Failed: "Не удалось скопировать, пожалуйста, предоставьте разрешение на доступ к буферу обмена", + }, + Context: { + Toast: (x: any) => `С ${x} контекстными подсказками`, + Edit: "Контекстные и памятные подсказки", + Add: "Добавить подсказку", + }, + Plugin: { + Name: "Плагин", + }, + Mask: { + Name: "Маска", + Page: { + Title: "Шаблон подсказки", + SubTitle: (count: number) => `${count} шаблонов подсказок`, + Search: "Поиск шаблонов", + Create: "Создать", + }, + Item: { + Info: (count: number) => `${count} подсказок`, + Chat: "Чат", + View: "Просмотр", + Edit: "Редактировать", + Delete: "Удалить", + DeleteConfirm: "Подтвердить удаление?", + }, + EditModal: { + Title: (readonly: boolean) => + `Редактирование шаблона подсказки ${readonly ? "(только для чтения)" : ""}`, + Download: "Скачать", + Clone: "Клонировать", + }, + Config: { + Avatar: "Аватар бота", + Name: "Имя бота", + }, + }, + NewChat: { + Return: "Вернуться", + Skip: "Пропустить", + Title: "Выберите маску", + SubTitle: "Общайтесь с душой за маской", + More: "Найти еще", + NotShow: "Не показывать снова", + ConfirmNoShow: "Подтвердите отключение? Вы можете включить это позже в настройках.", + }, + + UI: { + Confirm: "Подтвердить", + Cancel: "Отмена", + Close: "Закрыть", + Create: "Создать", + Edit: "Редактировать", + }, +}; + +export default ru; diff --git a/app/locales/tr.ts b/app/locales/tr.ts index 6793beb9..80d2bae7 100644 --- a/app/locales/tr.ts +++ b/app/locales/tr.ts @@ -70,7 +70,7 @@ const tr: LocaleType = { }, Lang: { Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language` - All: "All Languages", + All: "Tüm Diller", Options: { cn: "简体中文", en: "English", @@ -81,6 +81,7 @@ const tr: LocaleType = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "Avatar", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index c541e972..ba54e835 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -67,7 +67,7 @@ const tw: LocaleType = { ConfirmClearAll: "您確定要清除所有数据嗎?", }, Lang: { - Name: "Language", + Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language` All: "所有语言", Options: { cn: "简体中文", @@ -79,6 +79,7 @@ const tw: LocaleType = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "大頭貼", diff --git a/app/locales/vi.ts b/app/locales/vi.ts index 66d4a4d5..516f5792 100644 --- a/app/locales/vi.ts +++ b/app/locales/vi.ts @@ -81,6 +81,7 @@ const vi: LocaleType = { jp: "日本語", de: "Deutsch", vi: "Vietnamese", + ru: "Русский", }, }, Avatar: "Ảnh đại diện", diff --git a/app/utils.ts b/app/utils.ts index 1e34a6b3..a272d568 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -158,15 +158,15 @@ export function autoGrowTextArea(dom: HTMLTextAreaElement) { const width = getDomContentWidth(dom); measureDom.style.width = width + "px"; - measureDom.innerText = dom.value.trim().length > 0 ? dom.value : "1"; - - const emptyLineWrap = Math.max(0, dom.value.split("\n\n").length - 1); + measureDom.innerText = dom.value !== "" ? dom.value : "1"; + const endWithEmptyLine = dom.value.endsWith("\n"); const height = parseFloat(window.getComputedStyle(measureDom).height); const singleLineHeight = parseFloat( window.getComputedStyle(singleLineDom).height, ); - const rows = Math.round(height / singleLineHeight) + emptyLineWrap; + const rows = + Math.round(height / singleLineHeight) + (endWithEmptyLine ? 1 : 0); return rows; } diff --git a/docs/cloudflare-pages-cn.md b/docs/cloudflare-pages-cn.md new file mode 100644 index 00000000..2f9a99f2 --- /dev/null +++ b/docs/cloudflare-pages-cn.md @@ -0,0 +1,39 @@ +# Cloudflare Pages 部署指南 + +## 如何新建项目 +在 Github 上 fork 本项目,然后登录到 dash.cloudflare.com 并进入 Pages。 + +1. 点击 "Create a project"。 +2. 选择 "Connect to Git"。 +3. 关联 Cloudflare Pages 和你的 GitHub 账号。 +4. 选中你 fork 的此项目。 +5. 点击 "Begin setup"。 +6. 对于 "Project name" 和 "Production branch",可以使用默认值,也可以根据需要进行更改。 +7. 在 "Build Settings" 中,选择 "Framework presets" 选项并选择 "Next.js"。 +8. 由于 node:buffer 的 bug,暂时不要使用默认的 "Build command"。请使用以下命令: + ``` + npx https://prerelease-registry.devprod.cloudflare.dev/next-on-pages/runs/4930842298/npm-package-next-on-pages-230 --experimental-minify + ``` +9. 对于 "Build output directory",使用默认值并且不要修改。 +10. 不要修改 "Root Directory"。 +11. 对于 "Environment variables",点击 ">" 然后点击 "Add variable"。按照以下信息填写: + + - `NODE_VERSION=20.1` + - `NEXT_TELEMETRY_DISABLE=1` + - `OPENAI_API_KEY=你自己的API Key` + - `YARN_VERSION=1.22.19` + - `PHP_VERSION=7.4` + + 根据实际需要,可以选择填写以下选项: + + - `CODE= 可选填,访问密码,可以使用逗号隔开多个密码` + - `OPENAI_ORG_ID= 可选填,指定 OpenAI 中的组织 ID` + - `HIDE_USER_API_KEY=1 可选,不让用户自行填入 API Key` + - `DISABLE_GPT4=1 可选,不让用户使用 GPT-4` + +12. 点击 "Save and Deploy"。 +13. 点击 "Cancel deployment",因为需要填写 Compatibility flags。 +14. 前往 "Build settings"、"Functions",找到 "Compatibility flags"。 +15. 在 "Configure Production compatibility flag" 和 "Configure Preview compatibility flag" 中填写 "nodejs_compat"。 +16. 前往 "Deployments",点击 "Retry deployment"。 +17. Enjoy. \ No newline at end of file diff --git a/docs/cloudflare-pages-en.md b/docs/cloudflare-pages-en.md new file mode 100644 index 00000000..ee8ff6a6 --- /dev/null +++ b/docs/cloudflare-pages-en.md @@ -0,0 +1,38 @@ +# Cloudflare Pages Deployment Guide + +## How to create a new project +Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages. + +1. Click "Create a project". +2. Choose "Connect to Git". +3. Connect Cloudflare Pages to your GitHub account. +4. Select the forked project. +5. Click "Begin setup". +6. For "Project name" and "Production branch", use the default values or change them as needed. +7. In "Build Settings", choose the "Framework presets" option and select "Next.js". +8. Do not use the default "Build command" due to a node:buffer bug. Instead, use the following command: + ``` + npx https://prerelease-registry.devprod.cloudflare.dev/next-on-pages/runs/4930842298/npm-package-next-on-pages-230 --experimental-minify + ``` +9. For "Build output directory", use the default value and do not modify it. +10. Do not modify "Root Directory". +11. For "Environment variables", click ">" and then "Add variable". Fill in the following information: + - `NODE_VERSION=20.1` + - `NEXT_TELEMETRY_DISABLE=1` + - `OPENAI_API_KEY=your_own_API_key` + - `YARN_VERSION=1.22.19` + - `PHP_VERSION=7.4` + + Optionally fill in the following based on your needs: + + - `CODE= Optional, access passwords, multiple passwords can be separated by commas` + - `OPENAI_ORG_ID= Optional, specify the organization ID in OpenAI` + - `HIDE_USER_API_KEY=1 Optional, do not allow users to enter their own API key` + - `DISABLE_GPT4=1 Optional, do not allow users to use GPT-4` + +12. Click "Save and Deploy". +13. Click "Cancel deployment" because you need to fill in Compatibility flags. +14. Go to "Build settings", "Functions", and find "Compatibility flags". +15. Fill in "nodejs_compat" for both "Configure Production compatibility flag" and "Configure Preview compatibility flag". +16. Go to "Deployments" and click "Retry deployment". +17. Enjoy. \ No newline at end of file