feat(localization): added zh_TW (#2913)

* feat(localization): added zh_TW

* fixed based on @coderabbitai

* updated false translation for zh_TW

* new workflow

* revert

* fixed a lot of translations

* turned most zh to zh-CN

* fallbacklang

* bruh

* eliminate ALL _

* fix: paths and other miscs thanks @Calcium-Ion

* fixed translation and temp fix for preferencessettings.js

* fixed translation error

* fixed issue about legacy support

* reverted stupid coderabbit's suggestion
This commit is contained in:
Oliver Tzeng
2026-02-11 20:37:53 +08:00
committed by GitHub
parent fb5bc7c4f2
commit 6597610395
15 changed files with 3724 additions and 180 deletions

View File

@@ -7,9 +7,10 @@
🍥 **Passerelle de modèles étendus de nouvelle génération et système de gestion d'actifs d'IA**
<p align="center">
<a href="./README.zh.md">中文</a> |
<a href="./README.md">English</a> |
<strong>Français</strong> |
<a href="./README.zh_CN.md">简体中文</a> |
<a href="./README.zh_TW.md">繁體中文</a> |
<a href="./README.md">English</a> |
<strong>Français</strong> |
<a href="./README.ja.md">日本語</a>
</p>
@@ -56,10 +57,10 @@
## 📝 Description du projet
> [!NOTE]
> [!NOTE]
> Il s'agit d'un projet open-source développé sur la base de [One API](https://github.com/songquanpeng/one-api)
> [!IMPORTANT]
> [!IMPORTANT]
> - Ce projet est uniquement destiné à des fins d'apprentissage personnel, sans garantie de stabilité ni de support technique.
> - Les utilisateurs doivent se conformer aux [Conditions d'utilisation](https://openai.com/policies/terms-of-use) d'OpenAI et aux **lois et réglementations applicables**, et ne doivent pas l'utiliser à des fins illégales.
> - Conformément aux [《Mesures provisoires pour la gestion des services d'intelligence artificielle générative》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm), veuillez ne fournir aucun service d'IA générative non enregistré au public en Chine.
@@ -189,7 +190,7 @@ docker run --name new-api -d --restart always \
| Fonctionnalité | Description |
|------|------|
| 🎨 Nouvelle interface utilisateur | Conception d'interface utilisateur moderne |
| 🌍 Multilingue | Prend en charge le chinois, l'anglais, le français, le japonais |
| 🌍 Multilingue | Prend en charge le chinois simplifié, le chinois traditionnel, l'anglais, le français et le japonais |
| 🔄 Compatibilité des données | Complètement compatible avec la base de données originale de One API |
| 📈 Tableau de bord des données | Console visuelle et analyse statistique |
| 🔒 Gestion des permissions | Regroupement de jetons, restrictions de modèles, gestion des utilisateurs |
@@ -375,7 +376,7 @@ docker run --name new-api -d --restart always \
calciumion/new-api:latest
```
> **💡 Explication du chemin:**
> **💡 Explication du chemin:**
> - `./data:/data` - Chemin relatif, données sauvegardées dans le dossier data du répertoire actuel
> - Vous pouvez également utiliser un chemin absolu, par exemple : `/your/custom/path:/data`

View File

@@ -7,9 +7,10 @@
🍥 **次世代大規模モデルゲートウェイとAI資産管理システム**
<p align="center">
<a href="./README.zh.md">中文</a> |
<a href="./README.md">English</a> |
<a href="./README.fr.md">Français</a> |
<a href="./README.zh_CN.md">简体中文</a> |
<a href="./README.zh_TW.md">繁體中文</a> |
<a href="./README.md">English</a> |
<a href="./README.fr.md">Français</a> |
<strong>日本語</strong>
</p>
@@ -56,10 +57,10 @@
## 📝 プロジェクト説明
> [!NOTE]
> [!NOTE]
> 本プロジェクトは、[One API](https://github.com/songquanpeng/one-api)をベースに二次開発されたオープンソースプロジェクトです
> [!IMPORTANT]
> [!IMPORTANT]
> - 本プロジェクトは個人学習用のみであり、安定性の保証や技術サポートは提供しません。
> - ユーザーは、OpenAIの[利用規約](https://openai.com/policies/terms-of-use)および**法律法規**を遵守する必要があり、違法な目的で使用してはいけません。
> - [《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)の要求に従い、中国地域の公衆に未登録の生成式AI サービスを提供しないでください。
@@ -189,7 +190,7 @@ docker run --name new-api -d --restart always \
| 機能 | 説明 |
|------|------|
| 🎨 新しいUI | モダンなユーザーインターフェースデザイン |
| 🌍 多言語 | 中国語、英語、フランス語、日本語をサポート |
| 🌍 多言語 | 簡体字中国語、繁体字中国語、英語、フランス語、日本語をサポート |
| 🔄 データ互換性 | オリジナルのOne APIデータベースと完全に互換性あり |
| 📈 データダッシュボード | ビジュアルコンソールと統計分析 |
| 🔒 権限管理 | トークングループ化、モデル制限、ユーザー管理 |
@@ -377,7 +378,7 @@ docker run --name new-api -d --restart always \
calciumion/new-api:latest
```
> **💡 パス説明:**
> **💡 パス説明:**
> - `./data:/data` - 相対パス、データは現在のディレクトリのdataフォルダに保存されます
> - 絶対パスを使用することもできます:`/your/custom/path:/data`

View File

@@ -7,9 +7,10 @@
🍥 **Next-Generation LLM Gateway and AI Asset Management System**
<p align="center">
<a href="./README.zh.md">中文</a> |
<strong>English</strong> |
<a href="./README.fr.md">Français</a> |
<a href="./README.zh_CN.md">简体中文</a> |
<a href="./README.zh_TW.md">繁體中文</a> |
<strong>English</strong> |
<a href="./README.fr.md">Français</a> |
<a href="./README.ja.md">日本語</a>
</p>
@@ -56,10 +57,10 @@
## 📝 Project Description
> [!NOTE]
> [!NOTE]
> This is an open-source project developed based on [One API](https://github.com/songquanpeng/one-api)
> [!IMPORTANT]
> [!IMPORTANT]
> - This project is for personal learning purposes only, with no guarantee of stability or technical support
> - Users must comply with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**, and must not use it for illegal purposes
> - According to the [《Interim Measures for the Management of Generative Artificial Intelligence Services》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm), please do not provide any unregistered generative AI services to the public in China.
@@ -189,7 +190,7 @@ docker run --name new-api -d --restart always \
| Feature | Description |
|------|------|
| 🎨 New UI | Modern user interface design |
| 🌍 Multi-language | Supports Chinese, English, French, Japanese |
| 🌍 Multi-language | Supports Simplified Chinese, Traditional Chinese, English, French, Japanese |
| 🔄 Data Compatibility | Fully compatible with the original One API database |
| 📈 Data Dashboard | Visual console and statistical analysis |
| 🔒 Permission Management | Token grouping, model restrictions, user management |
@@ -375,7 +376,7 @@ docker run --name new-api -d --restart always \
calciumion/new-api:latest
```
> **💡 Path explanation:**
> **💡 Path explanation:**
> - `./data:/data` - Relative path, data saved in the data folder of the current directory
> - You can also use absolute path, e.g.: `/your/custom/path:/data`

View File

@@ -7,9 +7,10 @@
🍥 **新一代大模型网关与AI资产管理系统**
<p align="center">
<strong>中文</strong> |
<a href="./README.md">English</a> |
<a href="./README.fr.md">Français</a> |
简体中文 |
<a href="./README.zh_TW.md">繁體中文</a> |
<a href="./README.md">English</a> |
<a href="./README.fr.md">Français</a> |
<a href="./README.ja.md">日本語</a>
</p>
@@ -56,10 +57,10 @@
## 📝 项目说明
> [!NOTE]
> [!NOTE]
> 本项目为开源项目,在 [One API](https://github.com/songquanpeng/one-api) 的基础上进行二次开发
> [!IMPORTANT]
> [!IMPORTANT]
> - 本项目仅供个人学习使用,不保证稳定性,且不提供任何技术支持
> - 使用者必须在遵循 OpenAI 的 [使用条款](https://openai.com/policies/terms-of-use) 以及**法律法规**的情况下使用,不得用于非法用途
> - 根据 [《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm) 的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务
@@ -375,7 +376,7 @@ docker run --name new-api -d --restart always \
calciumion/new-api:latest
```
> **💡 路径说明:**
> **💡 路径说明:**
> - `./data:/data` - 相对路径,数据保存在当前目录的 data 文件夹
> - 也可使用绝对路径,如:`/your/custom/path:/data`

477
README.zh_TW.md Normal file
View File

@@ -0,0 +1,477 @@
<div align="center">
![new-api](/web/public/logo.png)
# New API
🍥 **新一代大模型網關與AI資產管理系統**
<p align="center">
繁體中文 |
<a href="./README.zh_CN.md">简体中文</a> |
<a href="./README.md">English</a> |
<a href="./README.fr.md">Français</a> |
<a href="./README.ja.md">日本語</a>
</p>
<p align="center">
<a href="https://raw.githubusercontent.com/Calcium-Ion/new-api/main/LICENSE">
<img src="https://img.shields.io/github/license/Calcium-Ion/new-api?color=brightgreen" alt="license">
</a>
<a href="https://github.com/Calcium-Ion/new-api/releases/latest">
<img src="https://img.shields.io/github/v/release/Calcium-Ion/new-api?color=brightgreen&include_prereleases" alt="release">
</a>
<a href="https://github.com/users/Calcium-Ion/packages/container/package/new-api">
<img src="https://img.shields.io/badge/docker-ghcr.io-blue" alt="docker">
</a>
<a href="https://hub.docker.com/r/CalciumIon/new-api">
<img src="https://img.shields.io/badge/docker-dockerHub-blue" alt="docker">
</a>
<a href="https://goreportcard.com/report/github.com/Calcium-Ion/new-api">
<img src="https://goreportcard.com/badge/github.com/Calcium-Ion/new-api" alt="GoReportCard">
</a>
</p>
<p align="center">
<a href="https://trendshift.io/repositories/8227" target="_blank">
<img src="https://trendshift.io/api/badge/repositories/8227" alt="Calcium-Ion%2Fnew-api | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
</a>
<br>
<a href="https://hellogithub.com/repository/QuantumNous/new-api" target="_blank">
<img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=539ac4217e69431684ad4a0bab768811&claim_uid=tbFPfKIDHpc4TzR" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" />
</a>
<a href="https://www.producthunt.com/products/new-api/launches/new-api?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-new-api" target="_blank" rel="noopener noreferrer">
<img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1047693&theme=light&t=1769577875005" alt="New API - All-in-one AI asset management gateway. | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" />
</a>
</p>
<p align="center">
<a href="#-快速開始">快速開始</a> •
<a href="#-主要特性">主要特性</a> •
<a href="#-部署">部署</a> •
<a href="#-文件">文件</a> •
<a href="#-幫助支援">幫助</a>
</p>
</div>
## 📝 項目說明
> [!NOTE]
> 本項目為開源項目,在 [One API](https://github.com/songquanpeng/one-api) 的基礎上進行二次開發
> [!IMPORTANT]
> - 本項目僅供個人學習使用,不保證穩定性,且不提供任何技術支援
> - 使用者必須在遵循 OpenAI 的 [使用條款](https://openai.com/policies/terms-of-use) 以及**法律法規**的情況下使用,不得用於非法用途
> - 根據 [《生成式人工智慧服務管理暫行辦法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm) 的要求,請勿對中國地區公眾提供一切未經備案的生成式人工智慧服務
---
## 🤝 我們信任的合作伙伴
<p align="center">
<em>排名不分先後</em>
</p>
<p align="center">
<a href="https://www.cherry-ai.com/" target="_blank">
<img src="./docs/images/cherry-studio.png" alt="Cherry Studio" height="80" />
</a>
<a href="https://bda.pku.edu.cn/" target="_blank">
<img src="./docs/images/pku.png" alt="北京大學" height="80" />
</a>
<a href="https://www.compshare.cn/?ytag=GPU_yy_gh_newapi" target="_blank">
<img src="./docs/images/ucloud.png" alt="UCloud 優刻得" height="80" />
</a>
<a href="https://www.aliyun.com/" target="_blank">
<img src="./docs/images/aliyun.png" alt="阿里雲" height="80" />
</a>
<a href="https://io.net/" target="_blank">
<img src="./docs/images/io-net.png" alt="IO.NET" height="80" />
</a>
</p>
---
## 🙏 特別鳴謝
<p align="center">
<a href="https://www.jetbrains.com/?from=new-api" target="_blank">
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo" width="120" />
</a>
</p>
<p align="center">
<strong>感謝 <a href="https://www.jetbrains.com/?from=new-api">JetBrains</a> 為本項目提供免費的開源開發許可證</strong>
</p>
---
## 🚀 快速開始
### 使用 Docker Compose推薦
```bash
# 複製項目
git clone https://github.com/QuantumNous/new-api.git
cd new-api
# 編輯 docker-compose.yml 配置
nano docker-compose.yml
# 啟動服務
docker-compose up -d
```
<details>
<summary><strong>使用 Docker 命令</strong></summary>
```bash
# 拉取最新鏡像
docker pull calciumion/new-api:latest
# 使用 SQLite預設
docker run --name new-api -d --restart always \
-p 3000:3000 \
-e TZ=Asia/Shanghai \
-v ./data:/data \
calciumion/new-api:latest
# 使用 MySQL
docker run --name new-api -d --restart always \
-p 3000:3000 \
-e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
-e TZ=Asia/Shanghai \
-v ./data:/data \
calciumion/new-api:latest
```
> **💡 提示:** `-v ./data:/data` 會將數據保存在當前目錄的 `data` 資料夾中,你也可以改為絕對路徑如 `-v /your/custom/path:/data`
</details>
---
🎉 部署完成後,訪問 `http://localhost:3000` 即可使用!
📖 更多部署方式請參考 [部署指南](https://docs.newapi.pro/zh/docs/installation)
---
## 📚 文件
<div align="center">
### 📖 [官方文件](https://docs.newapi.pro/zh/docs) | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/QuantumNous/new-api)
</div>
**快速導航:**
| 分類 | 連結 |
|------|------|
| 🚀 部署指南 | [安裝文件](https://docs.newapi.pro/zh/docs/installation) |
| ⚙️ 環境配置 | [環境變數](https://docs.newapi.pro/zh/docs/installation/config-maintenance/environment-variables) |
| 📡 接口文件 | [API 文件](https://docs.newapi.pro/zh/docs/api) |
| ❓ 常見問題 | [FAQ](https://docs.newapi.pro/zh/docs/support/faq) |
| 💬 社群交流 | [交流管道](https://docs.newapi.pro/zh/docs/support/community-interaction) |
---
## ✨ 主要特性
> 詳細特性請參考 [特性說明](https://docs.newapi.pro/zh/docs/guide/wiki/basic-concepts/features-introduction)
### 🎨 核心功能
| 特性 | 說明 |
|------|------|
| 🎨 全新 UI | 現代化的用戶界面設計 |
| 🌍 多語言 | 支援簡體中文、繁體中文、英文、法語、日語 |
| 🔄 數據兼容 | 完全兼容原版 One API 資料庫 |
| 📈 數據看板 | 視覺化控制檯與統計分析 |
| 🔒 權限管理 | 令牌分組、模型限制、用戶管理 |
### 💰 支付與計費
- ✅ 在線儲值易支付、Stripe
- ✅ 模型按次數收費
- ✅ 快取計費支援OpenAI、Azure、DeepSeek、Claude、Qwen等所有支援的模型
- ✅ 靈活的計費策略配置
### 🔐 授權與安全
- 😈 Discord 授權登錄
- 🤖 LinuxDO 授權登錄
- 📱 Telegram 授權登錄
- 🔑 OIDC 統一認證
- 🔍 Key 查詢使用額度(配合 [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool)
### 🚀 高級功能
**API 格式支援:**
- ⚡ [OpenAI Responses](https://docs.newapi.pro/zh/docs/api/ai-model/chat/openai/create-response)
- ⚡ [OpenAI Realtime API](https://docs.newapi.pro/zh/docs/api/ai-model/realtime/create-realtime-session)(含 Azure
- ⚡ [Claude Messages](https://docs.newapi.pro/zh/docs/api/ai-model/chat/create-message)
- ⚡ [Google Gemini](https://doc.newapi.pro/api/google-gemini-chat)
- 🔄 [Rerank 模型](https://docs.newapi.pro/zh/docs/api/ai-model/rerank/create-rerank)Cohere、Jina
**智慧路由:**
- ⚖️ 管道加權隨機
- 🔄 失敗自動重試
- 🚦 用戶級別模型限流
**格式轉換:**
- 🔄 **OpenAI Compatible ⇄ Claude Messages**
- 🔄 **OpenAI Compatible → Google Gemini**
- 🔄 **Google Gemini → OpenAI Compatible** - 僅支援文本,暫不支援函數調用
- 🚧 **OpenAI Compatible ⇄ OpenAI Responses** - 開發中
- 🔄 **思考轉內容功能**
**Reasoning Effort 支援:**
<details>
<summary>查看詳細配置</summary>
**OpenAI 系列模型:**
- `o3-mini-high` - High reasoning effort
- `o3-mini-medium` - Medium reasoning effort
- `o3-mini-low` - Low reasoning effort
- `gpt-5-high` - High reasoning effort
- `gpt-5-medium` - Medium reasoning effort
- `gpt-5-low` - Low reasoning effort
**Claude 思考模型:**
- `claude-3-7-sonnet-20250219-thinking` - 啟用思考模式
**Google Gemini 系列模型:**
- `gemini-2.5-flash-thinking` - 啟用思考模式
- `gemini-2.5-flash-nothinking` - 禁用思考模式
- `gemini-2.5-pro-thinking` - 啟用思考模式
- `gemini-2.5-pro-thinking-128` - 啟用思考模式並設置思考預算為128tokens
- 也可以直接在 Gemini 模型名稱後追加 `-low` / `-medium` / `-high` 來控制思考力道(無需再設置思考預算後綴)
</details>
---
## 🤖 模型支援
> 詳情請參考 [接口文件 - 中繼接口](https://docs.newapi.pro/zh/docs/api)
| 模型類型 | 說明 | 文件 |
|---------|------|------|
| 🤖 OpenAI-Compatible | OpenAI 兼容模型 | [文件](https://docs.newapi.pro/zh/docs/api/ai-model/chat/openai/createchatcompletion) |
| 🤖 OpenAI Responses | OpenAI Responses 格式 | [文件](https://docs.newapi.pro/zh/docs/api/ai-model/chat/openai/createresponse) |
| 🎨 Midjourney-Proxy | [Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy) | [文件](https://doc.newapi.pro/api/midjourney-proxy-image) |
| 🎵 Suno-API | [Suno API](https://github.com/Suno-API/Suno-API) | [文件](https://doc.newapi.pro/api/suno-music) |
| 🔄 Rerank | Cohere、Jina | [文件](https://docs.newapi.pro/zh/docs/api/ai-model/rerank/create-rerank) |
| 💬 Claude | Messages 格式 | [文件](https://docs.newapi.pro/zh/docs/api/ai-model/chat/createmessage) |
| 🌐 Gemini | Google Gemini 格式 | [文件](https://docs.newapi.pro/zh/docs/api/ai-model/chat/gemini/geminirelayv1beta) |
| 🔧 Dify | ChatFlow 模式 | - |
| 🎯 自訂 | 支援完整調用位址 | - |
### 📡 支援的接口
<details>
<summary>查看完整接口列表</summary>
- [聊天接口 (Chat Completions)](https://docs.newapi.pro/zh/docs/api/ai-model/chat/openai/createchatcompletion)
- [響應接口 (Responses)](https://docs.newapi.pro/zh/docs/api/ai-model/chat/openai/createresponse)
- [圖像接口 (Image)](https://docs.newapi.pro/zh/docs/api/ai-model/images/openai/post-v1-images-generations)
- [音訊接口 (Audio)](https://docs.newapi.pro/zh/docs/api/ai-model/audio/openai/create-transcription)
- [影片接口 (Video)](https://docs.newapi.pro/zh/docs/api/ai-model/audio/openai/createspeech)
- [嵌入接口 (Embeddings)](https://docs.newapi.pro/zh/docs/api/ai-model/embeddings/createembedding)
- [重排序接口 (Rerank)](https://docs.newapi.pro/zh/docs/api/ai-model/rerank/creatererank)
- [即時對話 (Realtime)](https://docs.newapi.pro/zh/docs/api/ai-model/realtime/createrealtimesession)
- [Claude 聊天](https://docs.newapi.pro/zh/docs/api/ai-model/chat/createmessage)
- [Google Gemini 聊天](https://docs.newapi.pro/zh/docs/api/ai-model/chat/gemini/geminirelayv1beta)
</details>
---
## 🚢 部署
> [!TIP]
> **最新版 Docker 鏡像:** `calciumion/new-api:latest`
### 📋 部署要求
| 組件 | 要求 |
|------|------|
| **本地資料庫** | SQLiteDocker 需掛載 `/data` 目錄)|
| **遠端資料庫** | MySQL ≥ 5.7.8 或 PostgreSQL ≥ 9.6 |
| **容器引擎** | Docker / Docker Compose |
### ⚙️ 環境變數配置
<details>
<summary>常用環境變數配置</summary>
| 變數名 | 說明 | 預設值 |
|--------|--------------------------------------------------------------|--------|
| `SESSION_SECRET` | 會話密鑰(多機部署必須) | - |
| `CRYPTO_SECRET` | 加密密鑰Redis 必須) | - |
| `SQL_DSN` | 資料庫連接字符串 | - |
| `REDIS_CONN_STRING` | Redis 連接字符串 | - |
| `STREAMING_TIMEOUT` | 流式超時時間(秒) | `300` |
| `STREAM_SCANNER_MAX_BUFFER_MB` | 流式掃描器單行最大緩衝MB圖像生成等超大 `data:` 片段(如 4K 圖片 base64需適當調大 | `64` |
| `MAX_REQUEST_BODY_MB` | 請求體最大大小MB**解壓縮後**計;防止超大請求/zip bomb 導致記憶體暴漲),超過將返回 `413` | `32` |
| `AZURE_DEFAULT_API_VERSION` | Azure API 版本 | `2025-04-01-preview` |
| `ERROR_LOG_ENABLED` | 錯誤日誌開關 | `false` |
| `PYROSCOPE_URL` | Pyroscope 服務位址 | - |
| `PYROSCOPE_APP_NAME` | Pyroscope 應用名 | `new-api` |
| `PYROSCOPE_BASIC_AUTH_USER` | Pyroscope Basic Auth 用戶名 | - |
| `PYROSCOPE_BASIC_AUTH_PASSWORD` | Pyroscope Basic Auth 密碼 | - |
| `PYROSCOPE_MUTEX_RATE` | Pyroscope mutex 採樣率 | `5` |
| `PYROSCOPE_BLOCK_RATE` | Pyroscope block 採樣率 | `5` |
| `HOSTNAME` | Pyroscope 標籤裡的主機名 | `new-api` |
📖 **完整配置:** [環境變數文件](https://docs.newapi.pro/zh/docs/installation/config-maintenance/environment-variables)
</details>
### 🔧 部署方式
<details>
<summary><strong>方式 1Docker Compose推薦</strong></summary>
```bash
# 複製項目
git clone https://github.com/QuantumNous/new-api.git
cd new-api
# 編輯配置
nano docker-compose.yml
# 啟動服務
docker-compose up -d
```
</details>
<details>
<summary><strong>方式 2Docker 命令</strong></summary>
**使用 SQLite**
```bash
docker run --name new-api -d --restart always \
-p 3000:3000 \
-e TZ=Asia/Shanghai \
-v ./data:/data \
calciumion/new-api:latest
```
**使用 MySQL**
```bash
docker run --name new-api -d --restart always \
-p 3000:3000 \
-e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
-e TZ=Asia/Shanghai \
-v ./data:/data \
calciumion/new-api:latest
```
> **💡 路徑說明:**
> - `./data:/data` - 相對路徑,數據保存在當前目錄的 data 資料夾
> - 也可使用絕對路徑,如:`/your/custom/path:/data`
</details>
<details>
<summary><strong>方式 3寶塔面板</strong></summary>
1. 安裝寶塔面板(≥ 9.2.0 版本)
2. 在應用商店搜尋 **New-API**
3. 一鍵安裝
📖 [圖文教學](./docs/BT.md)
</details>
### ⚠️ 多機部署注意事項
> [!WARNING]
> - **必須設置** `SESSION_SECRET` - 否則登錄狀態不一致
> - **公用 Redis 必須設置** `CRYPTO_SECRET` - 否則數據無法解密
### 🔄 管道重試與快取
**重試配置:** `設置 → 運營設置 → 通用設置 → 失敗重試次數`
**快取配置:**
- `REDIS_CONN_STRING`Redis 快取(推薦)
- `MEMORY_CACHE_ENABLED`:記憶體快取
---
## 🔗 相關項目
### 上游項目
| 項目 | 說明 |
|------|------|
| [One API](https://github.com/songquanpeng/one-api) | 原版項目基礎 |
| [Midjourney-Proxy](https://github.com/novicezk/midjourney-proxy) | Midjourney 接口支援 |
### 配套工具
| 項目 | 說明 |
|------|------|
| [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool) | Key 額度查詢工具 |
| [new-api-horizon](https://github.com/Calcium-Ion/new-api-horizon) | New API 高性能優化版 |
---
## 💬 幫助支援
### 📖 文件資源
| 資源 | 連結 |
|------|------|
| 📘 常見問題 | [FAQ](https://docs.newapi.pro/zh/docs/support/faq) |
| 💬 社群交流 | [交流管道](https://docs.newapi.pro/zh/docs/support/community-interaction) |
| 🐛 回饋問題 | [問題回饋](https://docs.newapi.pro/zh/docs/support/feedback-issues) |
| 📚 完整文件 | [官方文件](https://docs.newapi.pro/zh/docs) |
### 🤝 貢獻指南
歡迎各種形式的貢獻!
- 🐛 報告 Bug
- 💡 提出新功能
- 📝 改進文件
- 🔧 提交程式碼
---
## 📜 許可證
本項目採用 [GNU Affero 通用公共許可證 v3.0 (AGPLv3)](./LICENSE) 授權。
如果您所在的組織政策不允許使用 AGPLv3 許可的軟體,或您希望規避 AGPLv3 的開源義務,請發送郵件至:[support@quantumnous.com](mailto:support@quantumnous.com)
---
## 🌟 Star History
<div align="center">
[![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
</div>
---
<div align="center">
### 💖 感謝使用 New API
如果這個項目對你有幫助,歡迎給我們一個 ⭐️ Star
**[官方文件](https://docs.newapi.pro/zh/docs)** • **[問題回饋](https://github.com/Calcium-Ion/new-api/issues)** • **[最新發布](https://github.com/Calcium-Ion/new-api/releases)**
<sub>Built with ❤️ by QuantumNous</sub>
</div>

View File

@@ -29,7 +29,7 @@ const (
func normalizeLocale(locale string) (string, bool) {
l := strings.ToLower(strings.TrimSpace(locale))
switch l {
case "en", "zh", "ja":
case "en", "zh-CN", "zh-TW", "ja":
return l, true
default:
return "", false

View File

@@ -16,7 +16,8 @@ import (
)
const (
LangZh = "zh"
LangZhCN = "zh-CN"
LangZhTW = "zh-TW"
LangEn = "en"
DefaultLang = LangEn // Fallback to English if language not supported
)
@@ -39,7 +40,7 @@ func Init() error {
bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
// Load embedded translation files
files := []string{"locales/zh.yaml", "locales/en.yaml"}
files := []string{"locales/zh-CN.yaml", "locales/zh-TW.yaml", "locales/en.yaml"}
for _, file := range files {
_, err := bundle.LoadMessageFileFS(localeFS, file)
if err != nil {
@@ -49,7 +50,8 @@ func Init() error {
}
// Pre-create localizers for supported languages
localizers[LangZh] = i18n.NewLocalizer(bundle, LangZh)
localizers[LangZhCN] = i18n.NewLocalizer(bundle, LangZhCN)
localizers[LangZhTW] = i18n.NewLocalizer(bundle, LangZhTW)
localizers[LangEn] = i18n.NewLocalizer(bundle, LangEn)
// Set the TranslateMessage function in common package
@@ -201,8 +203,10 @@ func normalizeLang(lang string) string {
// Handle common variations
switch {
case strings.HasPrefix(lang, "zh-tw"):
return LangZhTW
case strings.HasPrefix(lang, "zh"):
return LangZh
return LangZhCN
case strings.HasPrefix(lang, "en"):
return LangEn
default:
@@ -212,7 +216,7 @@ func normalizeLang(lang string) string {
// SupportedLanguages returns a list of supported language codes
func SupportedLanguages() []string {
return []string{LangZh, LangEn}
return []string{LangZhCN, LangZhTW, LangEn}
}
// IsSupported checks if a language code is supported

252
i18n/locales/zh-TW.yaml Normal file
View File

@@ -0,0 +1,252 @@
# Chinese (Traditional) translations
# 中文(繁體)翻譯檔案
# Common messages
common.invalid_params: "無效的參數"
common.database_error: "資料庫錯誤,請稍後重試"
common.retry_later: "請稍後重試"
common.generate_failed: "生成失敗"
common.not_found: "未找到"
common.unauthorized: "未授權"
common.forbidden: "無權限"
common.invalid_id: "無效的ID"
common.id_empty: "ID 為空!"
common.feature_disabled: "該功能未啟用"
common.operation_success: "操作成功"
common.operation_failed: "操作失敗"
common.update_success: "更新成功"
common.update_failed: "更新失敗"
common.create_success: "建立成功"
common.create_failed: "建立失敗"
common.delete_success: "刪除成功"
common.delete_failed: "刪除失敗"
common.already_exists: "已存在"
common.name_cannot_be_empty: "名稱不能為空"
# Token messages
token.name_too_long: "令牌名稱過長"
token.quota_negative: "額度值不能為負數"
token.quota_exceed_max: "額度值超出有效範圍,最大值為 {{.Max}}"
token.generate_failed: "生成令牌失敗"
token.get_info_failed: "獲取令牌資訊失敗,請稍後重試"
token.expired_cannot_enable: "令牌已過期,無法啟用,請先修改令牌過期時間,或者設定為永不過期"
token.exhausted_cannot_enable: "令牌可用額度已用盡,無法啟用,請先修改令牌剩餘額度,或者設定為無限額度"
token.invalid: "無效的令牌"
token.not_provided: "未提供令牌"
token.expired: "該令牌已過期"
token.exhausted: "該令牌額度已用盡 TokenStatusExhausted[sk-{{.Prefix}}***{{.Suffix}}]"
token.status_unavailable: "該令牌狀態不可用"
token.db_error: "無效的令牌,資料庫查詢出錯,請聯繫管理員"
# Redemption messages
redemption.name_length: "兌換碼名稱長度必須在1-20之間"
redemption.count_positive: "兌換碼個數必須大於0"
redemption.count_max: "一次兌換碼批量生成的個數不能大於 100"
redemption.create_failed: "建立兌換碼失敗,請稍後重試"
redemption.invalid: "無效的兌換碼"
redemption.used: "該兌換碼已被使用"
redemption.expired: "該兌換碼已過期"
redemption.failed: "兌換失敗,請稍後重試"
redemption.not_provided: "未提供兌換碼"
redemption.expire_time_invalid: "過期時間不能早於當前時間"
# User messages
user.password_login_disabled: "管理員關閉了密碼登錄"
user.register_disabled: "管理員關閉了新使用者註冊"
user.password_register_disabled: "管理員關閉了通過密碼進行註冊,請使用第三方帳號驗證的形式進行註冊"
user.username_or_password_empty: "使用者名或密碼為空"
user.username_or_password_error: "使用者名或密碼錯誤,或使用者已被封禁"
user.email_or_password_empty: "信箱位址或密碼為空!"
user.exists: "使用者名已存在,或已註銷"
user.not_exists: "使用者不存在"
user.disabled: "該使用者已被禁用"
user.session_save_failed: "無法保存對話,請重試"
user.require_2fa: "請輸入雙重驗證碼"
user.email_verification_required: "管理員開啟了信箱驗證,請輸入信箱位址和驗證碼"
user.verification_code_error: "驗證碼錯誤或已過期"
user.input_invalid: "輸入不合法 {{.Error}}"
user.no_permission_same_level: "無權獲取同級或更高等級使用者的資訊"
user.no_permission_higher_level: "無權更新同權限等級或更高權限等級的使用者資訊"
user.cannot_create_higher_level: "無法建立權限大於等於自己的使用者"
user.cannot_delete_root_user: "不能刪除超級管理員帳號"
user.cannot_disable_root_user: "無法禁用超級管理員使用者"
user.cannot_demote_root_user: "無法降級超級管理員使用者"
user.already_admin: "該使用者已經是管理員"
user.already_common: "該使用者已經是普通使用者"
user.admin_cannot_promote: "普通管理員使用者無法提升其他使用者為管理員"
user.original_password_error: "原密碼錯誤"
user.invite_quota_insufficient: "邀請額度不足!"
user.transfer_quota_minimum: "轉移額度最小為{{.Min}}"
user.transfer_success: "劃轉成功"
user.transfer_failed: "劃轉失敗 {{.Error}}"
user.topup_processing: "充值處理中,請稍後重試"
user.register_failed: "使用者註冊失敗或使用者ID獲取失敗"
user.default_token_failed: "生成預設令牌失敗"
user.aff_code_empty: "affCode 為空!"
user.email_empty: "email 為空!"
user.github_id_empty: "GitHub id 為空!"
user.discord_id_empty: "discord id 為空!"
user.oidc_id_empty: "oidc id 為空!"
user.wechat_id_empty: "WeChat id 為空!"
user.telegram_id_empty: "Telegram id 為空!"
user.telegram_not_bound: "該 Telegram 帳號未綁定"
user.linux_do_id_empty: "Linux DO id 為空!"
# Quota messages
quota.negative: "額度不能為負數!"
quota.exceed_max: "額度值超出有效範圍"
quota.insufficient: "額度不足"
quota.warning_invalid: "無效的預警類型"
quota.threshold_gt_zero: "預警閾值必須大於0"
# Subscription messages
subscription.not_enabled: "訂閱方案未啟用"
subscription.title_empty: "訂閱方案標題不能為空"
subscription.price_negative: "價格不能為負數"
subscription.price_max: "價格不能超過9999"
subscription.purchase_limit_negative: "購買上限不能為負數"
subscription.quota_negative: "總額度不能為負數"
subscription.group_not_exists: "升級分組不存在"
subscription.reset_cycle_gt_zero: "自訂重置週期需大於0秒"
subscription.purchase_max: "已達到該訂閱方案購買上限"
subscription.invalid_id: "無效的訂閱ID"
subscription.invalid_user_id: "無效的使用者ID"
# Payment messages
payment.not_configured: "當前管理員未設定支付資訊"
payment.method_not_exists: "不存在此支付方式"
payment.callback_error: "回調位址設定錯誤"
payment.create_failed: "建立訂單失敗"
payment.start_failed: "啟用支付失敗"
payment.amount_too_low: "訂閱方案金額過低"
payment.stripe_not_configured: "Stripe 未設定或密鑰無效"
payment.webhook_not_configured: "Webhook 未設定"
payment.price_id_not_configured: "該訂閱方案未設定 StripePriceId"
payment.creem_not_configured: "該訂閱方案未設定 CreemProductId"
# Topup messages
topup.not_provided: "未提供支付單號"
topup.order_not_exists: "充值訂單不存在"
topup.order_status: "充值訂單狀態錯誤"
topup.failed: "充值失敗,請稍後重試"
topup.invalid_quota: "無效的充值額度"
# Channel messages
channel.not_exists: "管道不存在"
channel.id_format_error: "管道ID格式錯誤"
channel.no_available_key: "沒有可用的管道密鑰"
channel.get_list_failed: "獲取管道列表失敗,請稍後重試"
channel.get_tags_failed: "獲取標籤失敗,請稍後重試"
channel.get_key_failed: "獲取管道密鑰失敗"
channel.get_ollama_failed: "獲取Ollama模型失敗"
channel.query_failed: "查詢管道失敗"
channel.no_valid_upstream: "無有效上游管道"
channel.upstream_saturated: "當前分組上游負載已飽和,請稍後再試"
channel.get_available_failed: "獲取分組 {{.Group}} 下模型 {{.Model}} 的可用管道失敗"
# Model messages
model.name_empty: "模型名稱不能為空"
model.name_exists: "模型名稱已存在"
model.id_missing: "缺少模型 ID"
model.get_list_failed: "獲取模型列表失敗,請稍後重試"
model.get_failed: "獲取上游模型失敗"
model.reset_success: "重置模型倍率成功"
# Vendor messages
vendor.name_empty: "供應商名稱不能為空"
vendor.name_exists: "供應商名稱已存在"
vendor.id_missing: "缺少供應商 ID"
# Group messages
group.name_type_empty: "組名稱和類型不能為空"
group.name_exists: "組名稱已存在"
group.id_missing: "缺少組 ID"
# Checkin messages
checkin.disabled: "簽到功能未啟用"
checkin.already_today: "今日已簽到"
checkin.failed: "簽到失敗,請稍後重試"
checkin.quota_failed: "簽到失敗:更新額度出錯"
# Passkey messages
passkey.create_failed: "無法建立 Passkey 憑證"
passkey.login_abnormal: "Passkey 登錄狀態異常"
passkey.update_failed: "Passkey 憑證更新失敗"
passkey.invalid_user_id: "無效的使用者 ID"
passkey.verify_failed: "Passkey 驗證失敗,請重試或聯繫管理員"
# 2FA messages
twofa.not_enabled: "使用者未啟用2FA"
twofa.user_id_empty: "使用者ID不能為空"
twofa.already_exists: "使用者已存在2FA設定"
twofa.record_id_empty: "2FA記錄ID不能為空"
twofa.code_invalid: "驗證碼或備用碼不正確"
# Rate limit messages
rate_limit.reached: "您已達到請求數限制:{{.Minutes}}分鐘內最多請求{{.Max}}次"
rate_limit.total_reached: "您已達到總請求數限制:{{.Minutes}}分鐘內最多請求{{.Max}}次,包括失敗次數"
# Setting messages
setting.invalid_type: "無效的預警類型"
setting.webhook_empty: "Webhook位址不能為空"
setting.webhook_invalid: "無效的Webhook位址"
setting.email_invalid: "無效的信箱位址"
setting.bark_url_empty: "Bark推送URL不能為空"
setting.bark_url_invalid: "無效的Bark推送URL"
setting.gotify_url_empty: "Gotify伺服器位址不能為空"
setting.gotify_token_empty: "Gotify令牌不能為空"
setting.gotify_url_invalid: "無效的Gotify伺服器位址"
setting.url_must_http: "URL必須以http://或https://開頭"
setting.saved: "設定已更新"
# Deployment messages (io.net)
deployment.not_enabled: "io.net 模型部署功能未啟用或 API 密鑰缺失"
deployment.id_required: "deployment ID 為必填項"
deployment.container_id_required: "container ID 為必填項"
deployment.name_empty: "deployment 名稱不能為空"
deployment.name_taken: "deployment 名稱已被使用,請選擇其他名稱"
deployment.hardware_id_required: "hardware_id 參數為必填項"
deployment.hardware_invalid_id: "無效的 hardware_id 參數"
deployment.api_key_required: "api_key 為必填項"
deployment.invalid_payload: "無效的請求內容"
deployment.not_found: "未找到容器詳情"
# Performance messages
performance.disk_cache_cleared: "不活躍的磁碟快取已清理"
performance.stats_reset: "統計資訊已重置"
performance.gc_executed: "GC 已執行"
# Ability messages
ability.db_corrupted: "資料庫一致性被破壞"
ability.repair_running: "已經有一個修復任務在運行中,請稍後再試"
# OAuth messages
oauth.invalid_code: "無效的授權碼"
oauth.get_user_error: "獲取使用者資訊失敗"
oauth.account_used: "該帳號已被其他使用者綁定"
oauth.unknown_provider: "未知的 OAuth 供應者"
oauth.state_invalid: "state 參數為空或不匹配"
oauth.not_enabled: "管理員未開啟通過 {{.Provider}} 登錄以及註冊"
oauth.user_deleted: "使用者已註銷"
oauth.user_banned: "使用者已被封禁"
oauth.bind_success: "綁定成功"
oauth.already_bound: "該 {{.Provider}} 帳號已被綁定"
oauth.connect_failed: "無法連接至 {{.Provider}} 伺服器,請稍後重試"
oauth.token_failed: "{{.Provider}} 獲取 Token 失敗,請檢查設定"
oauth.user_info_empty: "{{.Provider}} 獲取使用者資訊為空,請檢查設定"
oauth.trust_level_low: "Linux DO 信任等級未達到管理員設定的最低信任等級"
# Model layer error messages
redeem.failed: "兌換失敗,請稍後重試"
user.create_default_token_error: "建立預設令牌失敗"
common.uuid_duplicate: "請重試,系統生成的 UUID 竟然重複了!"
common.invalid_input: "輸入不合法"
# Custom OAuth provider messages
custom_oauth.not_found: "自訂 OAuth 供應者不存在"
custom_oauth.slug_empty: "標識符不能為空"
custom_oauth.slug_exists: "標識符已存在"
custom_oauth.name_empty: "供應者名稱不能為空"
custom_oauth.has_bindings: "無法刪除已有使用者綁定的供應者"
custom_oauth.binding_not_found: "OAuth 綁定不存在"
custom_oauth.provider_id_field_invalid: "無法從供應者響應中提取使用者 ID"

View File

@@ -29,12 +29,17 @@ const LanguageSelector = ({ currentLang, onLanguageChange, t }) => {
<Dropdown.Menu className='!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600'>
{/* Language sorting: Order by English name (Chinese, English, French, Japanese, Russian) */}
<Dropdown.Item
onClick={() => onLanguageChange('zh')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
onClick={() => onLanguageChange('zh-CN')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh-CN' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>
中文
简体中文
</Dropdown.Item>
<Dropdown.Item
onClick={() => onLanguageChange('zh-TW')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh-TW' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>
繁體中文
</Dropdown.Item> <Dropdown.Item
onClick={() => onLanguageChange('en')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'en' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
>

View File

@@ -17,154 +17,160 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useState, useEffect, useContext } from 'react';
import { Card, Select, Typography, Avatar } from '@douyinfe/semi-ui';
import { Languages } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { API, showSuccess, showError } from '../../../../helpers';
import { UserContext } from '../../../../context/User';
import React, { useState, useEffect, useContext } from "react";
import { Card, Select, Typography, Avatar } from "@douyinfe/semi-ui";
import { Languages } from "lucide-react";
import { useTranslation } from "react-i18next";
import { API, showSuccess, showError } from "../../../../helpers";
import { UserContext } from "../../../../context/User";
// Language options with native names
const languageOptions = [
{ value: 'zh', label: '中文' },
{ value: 'en', label: 'English' },
{ value: 'fr', label: 'Français' },
{ value: 'ru', label: 'Русский' },
{ value: 'ja', label: '日本語' },
{ value: 'vi', label: 'Tiếng Việt' },
{ value: "zh-CN", label: "简体中文" },
{ value: "zh-TW", label: "繁體中文" },
{ value: "en", label: "English" },
{ value: 'fr', label: 'Français'},
{ value: 'ru', label: 'Русский'},
{ value: 'ja', label: '日本語'},
{ value: "vi", label: "Tiếng Việt" },
];
const PreferencesSettings = ({ t }) => {
const { i18n } = useTranslation();
const [userState, userDispatch] = useContext(UserContext);
const [currentLanguage, setCurrentLanguage] = useState(i18n.language || 'zh');
const [loading, setLoading] = useState(false);
const { i18n } = useTranslation();
const [userState, userDispatch] = useContext(UserContext);
const [currentLanguage, setCurrentLanguage] = useState(
i18n.language || "zh-CN",
);
const [loading, setLoading] = useState(false);
// Load saved language preference from user settings
useEffect(() => {
if (userState?.user?.setting) {
try {
const settings = JSON.parse(userState.user.setting);
if (settings.language) {
setCurrentLanguage(settings.language);
// Sync i18n with saved preference
if (i18n.language !== settings.language) {
i18n.changeLanguage(settings.language);
}
}
} catch (e) {
// Ignore parse errors
}
}
}, [userState?.user?.setting, i18n]);
// Load saved language preference from user settings
useEffect(() => {
if (userState?.user?.setting) {
try {
const settings = JSON.parse(userState.user.setting);
if (settings.language) {
// Normalize legacy "zh" to "zh-CN" for backward compatibility
const lang = settings.language === "zh" ? "zh-CN" : settings.language;
setCurrentLanguage(lang);
// Sync i18n with saved preference
if (i18n.language !== lang) {
i18n.changeLanguage(lang);
}
}
} catch (e) {
// Ignore parse errors
}
}
}, [userState?.user?.setting, i18n]);
const handleLanguagePreferenceChange = async (lang) => {
if (lang === currentLanguage) return;
const handleLanguagePreferenceChange = async (lang) => {
if (lang === currentLanguage) return;
setLoading(true);
const previousLang = currentLanguage;
setLoading(true);
const previousLang = currentLanguage;
try {
// Update language immediately for responsive UX
setCurrentLanguage(lang);
i18n.changeLanguage(lang);
try {
// Update language immediately for responsive UX
setCurrentLanguage(lang);
i18n.changeLanguage(lang);
// Save to backend
const res = await API.put('/api/user/self', {
language: lang,
});
// Save to backend
const res = await API.put("/api/user/self", {
language: lang,
});
if (res.data.success) {
showSuccess(t('语言偏好已保存'));
// Update user context with new setting
if (userState?.user?.setting) {
try {
const settings = JSON.parse(userState.user.setting);
settings.language = lang;
userDispatch({
type: 'login',
payload: {
...userState.user,
setting: JSON.stringify(settings),
},
});
} catch (e) {
// Ignore
}
}
} else {
showError(res.data.message || t('保存失败'));
// Revert on error
setCurrentLanguage(previousLang);
i18n.changeLanguage(previousLang);
}
} catch (error) {
showError(t('保存失败,请重试'));
// Revert on error
setCurrentLanguage(previousLang);
i18n.changeLanguage(previousLang);
} finally {
setLoading(false);
}
};
if (res.data.success) {
showSuccess(t("语言偏好已保存"));
// Update user context with new setting
if (userState?.user?.setting) {
try {
const settings = JSON.parse(userState.user.setting);
settings.language = lang;
userDispatch({
type: "login",
payload: {
...userState.user,
setting: JSON.stringify(settings),
},
});
} catch (e) {
// Ignore
}
}
} else {
showError(res.data.message || t("保存失败"));
// Revert on error
setCurrentLanguage(previousLang);
i18n.changeLanguage(previousLang);
}
} catch (error) {
showError(t("保存失败,请重试"));
// Revert on error
setCurrentLanguage(previousLang);
i18n.changeLanguage(previousLang);
} finally {
setLoading(false);
}
};
return (
<Card className='!rounded-2xl shadow-sm border-0'>
{/* Card Header */}
<div className='flex items-center mb-4'>
<Avatar size='small' color='violet' className='mr-3 shadow-md'>
<Languages size={16} />
</Avatar>
<div>
<Typography.Text className='text-lg font-medium'>
{t('偏好设置')}
</Typography.Text>
<div className='text-xs text-gray-600 dark:text-gray-400'>
{t('界面语言和其他个人偏好')}
</div>
</div>
</div>
return (
<Card className="!rounded-2xl shadow-sm border-0">
{/* Card Header */}
<div className="flex items-center mb-4">
<Avatar size="small" color="violet" className="mr-3 shadow-md">
<Languages size={16} />
</Avatar>
<div>
<Typography.Text className="text-lg font-medium">
{t("偏好设置")}
</Typography.Text>
<div className="text-xs text-gray-600 dark:text-gray-400">
{t("界面语言和其他个人偏好")}
</div>
</div>
</div>
{/* Language Setting Card */}
<Card className="!rounded-xl border dark:border-gray-700">
<div className="flex flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-4">
<div className="flex items-start w-full sm:w-auto">
<div className="w-12 h-12 rounded-full bg-violet-50 dark:bg-violet-900/30 flex items-center justify-center mr-4 flex-shrink-0">
<Languages
size={20}
className="text-violet-600 dark:text-violet-400"
/>
</div>
<div>
<Typography.Title heading={6} className="mb-1">
{t("语言偏好")}
</Typography.Title>
<Typography.Text type="tertiary" className="text-sm">
{t("选择您的首选界面语言,设置将自动保存并同步到所有设备")}
</Typography.Text>
</div>
</div>
<Select
value={currentLanguage}
onChange={handleLanguagePreferenceChange}
style={{ width: 180 }}
loading={loading}
optionList={languageOptions.map((opt) => ({
value: opt.value,
label: opt.label,
}))}
/>
</div>
</Card>
{/* Language Setting Card */}
<Card className='!rounded-xl border dark:border-gray-700'>
<div className='flex flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-4'>
<div className='flex items-start w-full sm:w-auto'>
<div className='w-12 h-12 rounded-full bg-violet-50 dark:bg-violet-900/30 flex items-center justify-center mr-4 flex-shrink-0'>
<Languages
size={20}
className='text-violet-600 dark:text-violet-400'
/>
</div>
<div>
<Typography.Title heading={6} className='mb-1'>
{t('语言偏好')}
</Typography.Title>
<Typography.Text type='tertiary' className='text-sm'>
{t('选择您的首选界面语言,设置将自动保存并同步到所有设备')}
</Typography.Text>
</div>
</div>
<Select
value={currentLanguage}
onChange={handleLanguagePreferenceChange}
style={{ width: 180 }}
loading={loading}
optionList={languageOptions.map((opt) => ({
value: opt.value,
label: opt.label,
}))}
/>
</div>
</Card>
{/* Additional info */}
<div className='mt-4 text-xs text-gray-500 dark:text-gray-400'>
<Typography.Text type='tertiary'>
{t('提示语言偏好会同步到您登录的所有设备并影响API返回的错误消息语言。')}
</Typography.Text>
</div>
</Card>
);
{/* Additional info */}
<div className="mt-4 text-xs text-gray-500 dark:text-gray-400">
<Typography.Text type="tertiary">
{t(
"提示语言偏好会同步到您登录的所有设备并影响API返回的错误消息语言。",
)}
</Typography.Text>
</div>
</Card>
);
};
export default PreferencesSettings;

View File

@@ -24,14 +24,14 @@ import { useIsMobile } from '../../../../hooks/common/useIsMobile';
const SyncWizardModal = ({ visible, onClose, onConfirm, loading, t }) => {
const [step, setStep] = useState(0);
const [option, setOption] = useState('official');
const [locale, setLocale] = useState('zh');
const [locale, setLocale] = useState('zh-CN');
const isMobile = useIsMobile();
useEffect(() => {
if (visible) {
setStep(0);
setOption('official');
setLocale('zh');
setLocale('zh-CN');
}
}, [visible]);
@@ -113,13 +113,16 @@ const SyncWizardModal = ({ visible, onClose, onConfirm, loading, t }) => {
name='sync-locale-selection'
>
<Radio value='en' extra='English'>
EN
en
</Radio>
<Radio value='zh' extra='中文'>
ZH
<Radio value='zh-CN' extra='简体中文'>
zh-CN
</Radio>
<Radio value='zh-TW' extra='繁體中文'>
zh-TW
</Radio>
<Radio value='ja' extra='日本語'>
JA
ja
</Radio>
</RadioGroup>
</div>

View File

@@ -23,7 +23,8 @@ import LanguageDetector from 'i18next-browser-languagedetector';
import enTranslation from './locales/en.json';
import frTranslation from './locales/fr.json';
import zhTranslation from './locales/zh.json';
import zhCNTranslation from './locales/zh-CN.json';
import zhTWTranslation from './locales/zh-TW.json';
import ruTranslation from './locales/ru.json';
import jaTranslation from './locales/ja.json';
import viTranslation from './locales/vi.json';
@@ -32,16 +33,17 @@ i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
load: 'languageOnly',
load: 'currentOnly',
resources: {
en: enTranslation,
zh: zhTranslation,
'zh-CN': zhCNTranslation,
'zh-TW': zhTWTranslation,
fr: frTranslation,
ru: ruTranslation,
ja: jaTranslation,
vi: viTranslation,
},
fallbackLng: 'zh',
fallbackLng: 'zh-CN',
nsSeparator: false,
interpolation: {
escapeValue: false,

File diff suppressed because it is too large Load Diff