Added es-ES (#1989)
* Additional English translations Provide an English version of the readme, and add a few translations that were missing elsewhere * Use browser language by default * Support 'en' and 'vi' as languages * Fixed: Browserslist: caniuse-lite is outdated. Full message was: Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest Why you should do it regularly: https://github.com/browserslist/update-db#readme * Added es-ES These changes were originally from https://github.com/rasta26/chatgpt-web although I did tweak the translations a bit. --------- Co-authored-by: Ed Burnette <ed.burnette@hiddenmind.ai>
This commit is contained in:
parent
15a6b19897
commit
60f1f71d27
|
@ -0,0 +1,14 @@
|
||||||
|
### docker-compose Deployment Tutorial
|
||||||
|
-Put the packaged front-end files in the `nginx/html` directory
|
||||||
|
- ```shell
|
||||||
|
# start up
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
- ```shell
|
||||||
|
# Check the running status
|
||||||
|
docker ps
|
||||||
|
```
|
||||||
|
- ```shell
|
||||||
|
# end run
|
||||||
|
docker-compose down
|
||||||
|
```
|
|
@ -3,35 +3,35 @@ version: '3'
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
container_name: chatgpt-web
|
container_name: chatgpt-web
|
||||||
image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可
|
image: chenzhaoyu94/chatgpt-web # Always use latest, just pull the tag image again when updating
|
||||||
ports:
|
ports:
|
||||||
- 3002:3002
|
- 3002:3002
|
||||||
environment:
|
environment:
|
||||||
# 二选一
|
# pick one of two
|
||||||
OPENAI_API_KEY:
|
OPENAI_API_KEY:
|
||||||
# 二选一
|
# pick one of two
|
||||||
OPENAI_ACCESS_TOKEN:
|
OPENAI_ACCESS_TOKEN:
|
||||||
# API接口地址,可选,设置 OPENAI_API_KEY 时可用
|
# API interface address, optional, available when OPENAI_API_KEY is set
|
||||||
OPENAI_API_BASE_URL:
|
OPENAI_API_BASE_URL:
|
||||||
# API模型,可选,设置 OPENAI_API_KEY 时可用
|
# API model, optional, available when OPENAI_API_KEY is set
|
||||||
OPENAI_API_MODEL:
|
OPENAI_API_MODEL:
|
||||||
# 反向代理,可选
|
# reverse proxy, optional
|
||||||
API_REVERSE_PROXY:
|
API_REVERSE_PROXY:
|
||||||
# 访问权限密钥,可选
|
# Access permission key, optional
|
||||||
AUTH_SECRET_KEY:
|
AUTH_SECRET_KEY:
|
||||||
# 每小时最大请求次数,可选,默认无限
|
# The maximum number of requests per hour, optional, default unlimited
|
||||||
MAX_REQUEST_PER_HOUR: 0
|
MAX_REQUEST_PER_HOUR: 0
|
||||||
# 超时,单位毫秒,可选
|
# timeout in milliseconds, optional
|
||||||
TIMEOUT_MS: 60000
|
TIMEOUT_MS: 60000
|
||||||
# Socks代理,可选,和 SOCKS_PROXY_PORT 一起时生效
|
# Socks proxy, optional, works with SOCKS_PROXY_PORT
|
||||||
SOCKS_PROXY_HOST:
|
SOCKS_PROXY_HOST:
|
||||||
# Socks代理端口,可选,和 SOCKS_PROXY_HOST 一起时生效
|
# Socks proxy port, optional, effective when combined with SOCKS_PROXY_HOST
|
||||||
SOCKS_PROXY_PORT:
|
SOCKS_PROXY_PORT:
|
||||||
# Socks代理用户名,可选,和 SOCKS_PROXY_HOST & SOCKS_PROXY_PORT 一起时生效
|
# Socks proxy username, optional, effective when combined with SOCKS_PROXY_HOST & SOCKS_PROXY_PORT
|
||||||
SOCKS_PROXY_USERNAME:
|
SOCKS_PROXY_USERNAME:
|
||||||
# Socks代理密码,可选,和 SOCKS_PROXY_HOST & SOCKS_PROXY_PORT 一起时生效
|
# Socks proxy password, optional, effective when combined with SOCKS_PROXY_HOST & SOCKS_PROXY_PORT
|
||||||
SOCKS_PROXY_PASSWORD:
|
SOCKS_PROXY_PASSWORD:
|
||||||
# HTTPS_PROXY 代理,可选
|
# HTTPS_PROXY proxy, optional
|
||||||
HTTPS_PROXY:
|
HTTPS_PROXY:
|
||||||
nginx:
|
nginx:
|
||||||
container_name: nginx
|
container_name: nginx
|
||||||
|
|
|
@ -4,7 +4,7 @@ server {
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|
||||||
# 防止爬虫抓取
|
# Prevent crawlers from crawling
|
||||||
if ($http_user_agent ~* "360Spider|JikeSpider|Spider|spider|bot|Bot|2345Explorer|curl|wget|webZIP|qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|NSPlayer|bingbot")
|
if ($http_user_agent ~* "360Spider|JikeSpider|Spider|spider|bot|Bot|2345Explorer|curl|wget|webZIP|qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|NSPlayer|bingbot")
|
||||||
{
|
{
|
||||||
return 403;
|
return 403;
|
||||||
|
@ -16,7 +16,7 @@ server {
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api {
|
location /api {
|
||||||
proxy_set_header X-Real-IP $remote_addr; #转发用户IP
|
proxy_set_header X-Real-IP $remote_addr; #Forward user IP
|
||||||
proxy_pass http://app:3002;
|
proxy_pass http://app:3002;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
### docker-compose 部署教程
|
|
||||||
- 将打包好的前端文件放到 `nginx/html` 目录下
|
|
||||||
- ```shell
|
|
||||||
# 启动
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
- ```shell
|
|
||||||
# 查看运行状态
|
|
||||||
docker ps
|
|
||||||
```
|
|
||||||
- ```shell
|
|
||||||
# 结束运行
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-cmn-Hans">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
"markdown-it-link-attributes": "^4.0.1",
|
"markdown-it-link-attributes": "^4.0.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"rimraf": "^4.2.0",
|
"rimraf": "^4.3.0",
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"typescript": "~4.9.5",
|
"typescript": "~4.9.5",
|
||||||
"vite": "^4.2.0",
|
"vite": "^4.2.0",
|
||||||
|
|
2485
pnpm-lock.yaml
2485
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -54,11 +54,13 @@ const themeOptions: { label: string; key: Theme; icon: string }[] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const languageOptions: { label: string; key: Language; value: Language }[] = [
|
const languageOptions: { label: string; key: Language; value: Language }[] = [
|
||||||
{ label: '简体中文', key: 'zh-CN', value: 'zh-CN' },
|
|
||||||
{ label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
|
|
||||||
{ label: 'English', key: 'en-US', value: 'en-US' },
|
{ label: 'English', key: 'en-US', value: 'en-US' },
|
||||||
|
{ label: 'Español', key: 'es-ES', value: 'es-ES' },
|
||||||
{ label: '한국어', key: 'ko-KR', value: 'ko-KR' },
|
{ label: '한국어', key: 'ko-KR', value: 'ko-KR' },
|
||||||
{ label: 'Русский язык', key: 'ru-RU', value: 'ru-RU' },
|
{ label: 'Русский язык', key: 'ru-RU', value: 'ru-RU' },
|
||||||
|
{ label: 'Tiếng Việt', key: 'vi-VN', value: 'vi-VN' },
|
||||||
|
{ label: '简体中文', key: 'zh-CN', value: 'zh-CN' },
|
||||||
|
{ label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
|
||||||
]
|
]
|
||||||
|
|
||||||
function updateUserInfo(options: Partial<UserInfo>) {
|
function updateUserInfo(options: Partial<UserInfo>) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { enUS, koKR, ruRU, viVN, zhCN, zhTW } from 'naive-ui'
|
import { enUS, esAR, koKR, ruRU, viVN, zhCN, zhTW } from 'naive-ui'
|
||||||
import { useAppStore } from '@/store'
|
import { useAppStore } from '@/store'
|
||||||
import { setLocale } from '@/locales'
|
import { setLocale } from '@/locales'
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ export function useLanguage() {
|
||||||
switch (appStore.language) {
|
switch (appStore.language) {
|
||||||
case 'en-US':
|
case 'en-US':
|
||||||
return enUS
|
return enUS
|
||||||
|
case 'es-ES':
|
||||||
|
return esAR
|
||||||
case 'ko-KR':
|
case 'ko-KR':
|
||||||
return koKR
|
return koKR
|
||||||
case 'vi-VN':
|
case 'vi-VN':
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
export default {
|
||||||
|
common: {
|
||||||
|
add: 'Agregar',
|
||||||
|
addSuccess: 'Agregado con éxito',
|
||||||
|
edit: 'Editar',
|
||||||
|
editSuccess: 'Edición exitosa',
|
||||||
|
delete: 'Borrar',
|
||||||
|
deleteSuccess: 'Borrado con éxito',
|
||||||
|
save: 'Guardar',
|
||||||
|
saveSuccess: 'Guardado con éxito',
|
||||||
|
reset: 'Reiniciar',
|
||||||
|
action: 'Acción',
|
||||||
|
export: 'Exportar',
|
||||||
|
exportSuccess: 'Exportación exitosa',
|
||||||
|
import: 'Importar',
|
||||||
|
importSuccess: 'Importación exitosa',
|
||||||
|
clear: 'Limpiar',
|
||||||
|
clearSuccess: 'Limpieza exitosa',
|
||||||
|
yes: 'Sí',
|
||||||
|
no: 'No',
|
||||||
|
confirm: 'Confirmar',
|
||||||
|
download: 'Descargar',
|
||||||
|
noData: 'Sin datos',
|
||||||
|
wrong: 'Algo salió mal, inténtalo de nuevo más tarde.',
|
||||||
|
success: 'Exitoso',
|
||||||
|
failed: 'Fallido',
|
||||||
|
verify: 'Verificar',
|
||||||
|
unauthorizedTips: 'No autorizado, por favor verifique primero.',
|
||||||
|
stopResponding: 'No responde',
|
||||||
|
},
|
||||||
|
chat: {
|
||||||
|
newChatButton: 'Nueva conversación',
|
||||||
|
newChatTitle: 'Nueva conversación',
|
||||||
|
placeholder: 'Pregúntame lo que sea...(Shift + Enter = salto de línea, "/" para activar avisos)',
|
||||||
|
placeholderMobile: 'Pregúntame lo que sea...',
|
||||||
|
copy: 'Copiar',
|
||||||
|
copied: 'Copiado',
|
||||||
|
copyCode: 'Copiar código',
|
||||||
|
copyFailed: 'Copia fallida',
|
||||||
|
clearChat: 'Limpiar chat',
|
||||||
|
clearChatConfirm: '¿Estás seguro de borrar este chat?',
|
||||||
|
exportImage: 'Exportar imagen',
|
||||||
|
exportImageConfirm: '¿Estás seguro de exportar este chat a png?',
|
||||||
|
exportSuccess: 'Exportación exitosa',
|
||||||
|
exportFailed: 'Exportación fallida',
|
||||||
|
usingContext: 'Modo de contexto',
|
||||||
|
turnOnContext: 'En el modo actual, el envío de mensajes llevará registros de chat anteriores.',
|
||||||
|
turnOffContext: 'En el modo actual, el envío de mensajes no incluirá registros de conversaciones anteriores.',
|
||||||
|
deleteMessage: 'Borrar mensaje',
|
||||||
|
deleteMessageConfirm: '¿Estás seguro de eliminar este mensaje?',
|
||||||
|
deleteHistoryConfirm: '¿Estás seguro de borrar esta historia?',
|
||||||
|
clearHistoryConfirm: '¿Estás seguro de borrar el historial de chat?',
|
||||||
|
preview: 'Avance',
|
||||||
|
showRawText: 'Mostrar como texto sin formato',
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
setting: 'Configuración',
|
||||||
|
general: 'General',
|
||||||
|
advanced: 'Avanzado',
|
||||||
|
config: 'Configurar',
|
||||||
|
avatarLink: 'Enlace de avatar',
|
||||||
|
name: 'Nombre',
|
||||||
|
description: 'Descripción',
|
||||||
|
role: 'Rol',
|
||||||
|
temperature: 'Temperatura',
|
||||||
|
top_p: 'Top_p',
|
||||||
|
resetUserInfo: 'Restablecer información de usuario',
|
||||||
|
chatHistory: 'Historial de chat',
|
||||||
|
theme: 'Tema',
|
||||||
|
language: 'Idioma',
|
||||||
|
api: 'API',
|
||||||
|
reverseProxy: 'Reverse Proxy',
|
||||||
|
timeout: 'Tiempo de espera',
|
||||||
|
socks: 'Socks',
|
||||||
|
httpsProxy: 'HTTPS Proxy',
|
||||||
|
balance: 'Saldo de API',
|
||||||
|
monthlyUsage: 'Uso mensual de API',
|
||||||
|
openSource: 'Este proyecto es de código abierto en',
|
||||||
|
freeMIT: 'gratis y basado en la licencia MIT, ¡sin ningún tipo de comportamiento de pago!',
|
||||||
|
stars: 'Si encuentras este proyecto útil, por favor dame una Estrella en GitHub o da un pequeño patrocinio, ¡gracias!',
|
||||||
|
},
|
||||||
|
store: {
|
||||||
|
siderButton: 'Tienda rápida',
|
||||||
|
local: 'Local',
|
||||||
|
online: 'En línea',
|
||||||
|
title: 'Título',
|
||||||
|
description: 'Descripción',
|
||||||
|
clearStoreConfirm: '¿Estás seguro de borrar los datos?',
|
||||||
|
importPlaceholder: 'Pegue los datos JSON aquí',
|
||||||
|
addRepeatTitleTips: 'Título duplicado, vuelva a ingresar',
|
||||||
|
addRepeatContentTips: 'Contenido duplicado: {msg}, por favor vuelva a entrar',
|
||||||
|
editRepeatTitleTips: 'Conflicto de título, revíselo',
|
||||||
|
editRepeatContentTips: 'Conflicto de contenido {msg} , por favor vuelva a modificar',
|
||||||
|
importError: 'Discrepancia de valor clave',
|
||||||
|
importRepeatTitle: 'Título saltado repetidamente: {msg}',
|
||||||
|
importRepeatContent: 'El contenido se salta repetidamente: {msg}',
|
||||||
|
onlineImportWarning: 'Nota: ¡Compruebe la fuente del archivo JSON!',
|
||||||
|
downloadError: 'Verifique el estado de la red y la validez del archivo JSON',
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import enUS from './en-US'
|
import enUS from './en-US'
|
||||||
|
import esES from './es-ES'
|
||||||
import koKR from './ko-KR'
|
import koKR from './ko-KR'
|
||||||
import ruRU from './ru-RU'
|
import ruRU from './ru-RU'
|
||||||
import viVN from './vi-VN'
|
import viVN from './vi-VN'
|
||||||
|
@ -19,6 +20,7 @@ const i18n = createI18n({
|
||||||
allowComposition: true,
|
allowComposition: true,
|
||||||
messages: {
|
messages: {
|
||||||
'en-US': enUS,
|
'en-US': enUS,
|
||||||
|
'es-ES': esES,
|
||||||
'ko-KR': koKR,
|
'ko-KR': koKR,
|
||||||
'ru-RU': ruRU,
|
'ru-RU': ruRU,
|
||||||
'vi-VN': viVN,
|
'vi-VN': viVN,
|
||||||
|
|
|
@ -4,11 +4,13 @@ const LOCAL_NAME = 'appSetting'
|
||||||
|
|
||||||
export type Theme = 'light' | 'dark' | 'auto'
|
export type Theme = 'light' | 'dark' | 'auto'
|
||||||
|
|
||||||
export type Language = 'en-US' | 'ko-KR' | 'ru-RU' | 'vi-VN' | 'zh-CN' | 'zh-TW'
|
export type Language = 'en-US' | 'es-ES' | 'ko-KR' | 'ru-RU' | 'vi-VN' | 'zh-CN' | 'zh-TW'
|
||||||
|
|
||||||
const languageMap: { [key: string]: Language } = {
|
const languageMap: { [key: string]: Language } = {
|
||||||
'en': 'en-US',
|
'en': 'en-US',
|
||||||
'en-US': 'en-US',
|
'en-US': 'en-US',
|
||||||
|
'es': 'es-ES',
|
||||||
|
'es-ES': 'es-ES',
|
||||||
'ko': 'ko-KR',
|
'ko': 'ko-KR',
|
||||||
'ko-KR': 'ko-KR',
|
'ko-KR': 'ko-KR',
|
||||||
'ru': 'ru-RU',
|
'ru': 'ru-RU',
|
||||||
|
|
|
@ -28,7 +28,7 @@ export function useScroll(): ScrollReturn {
|
||||||
const scrollToBottomIfAtBottom = async () => {
|
const scrollToBottomIfAtBottom = async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
if (scrollRef.value) {
|
if (scrollRef.value) {
|
||||||
const threshold = 100 // 阈值,表示滚动条到底部的距离阈值
|
const threshold = 100 // Threshold, indicating the distance threshold to the bottom of the scroll bar.
|
||||||
const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight
|
const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight
|
||||||
if (distanceToBottom <= threshold)
|
if (distanceToBottom <= threshold)
|
||||||
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
|
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
|
||||||
|
|
Loading…
Reference in New Issue