feat: rebrand to ConvertX-CN, fix TypeScript error, add docs

- Fix TypeScript TS2345 error in i18n/service.ts by parsing cookie from header
- Rebrand all ConvertX references to ConvertX-CN
- Change default language from en to zh-TW
- Fix footer to always display English 'Powered by ConvertX-CN'
- Update GitHub links to pi-docket/ConvertX-CN
- Add /docs folder with documentation:
  - getting-started.md
  - configuration.md
  - converters.md
  - i18n.md
- Update all locale files with new branding
This commit is contained in:
Your Name 2026-01-20 10:29:17 +08:00
parent a6770c0d51
commit 53b83b425f
18 changed files with 315 additions and 41 deletions

View file

@ -145,9 +145,9 @@ ConvertX-CN 支援以下語言:
| 語言代碼 | 語言名稱 |
| -------- | ---------------- |
| en | English預設 |
| zh-TW | 繁體中文 |
| zh-TW | 繁體中文(預設) |
| zh-CN | 简体中文 |
| en | English |
| ja | 日本語 |
| ko | 한국어 |

65
docs/configuration.md Normal file
View file

@ -0,0 +1,65 @@
# 環境變數設定
所有環境變數皆為選填,建議至少設定 `JWT_SECRET`
## 安全性設定
| 變數名稱 | 預設值 | 說明 |
| -------------------- | ------------ | ---------------------------------- |
| JWT_SECRET | randomUUID() | 用於簽署 JWT 的密鑰字串(請務必更改) |
| ACCOUNT_REGISTRATION | false | 是否允許註冊新帳號 |
| HTTP_ALLOWED | false | 是否允許 HTTP 連線(僅本地使用) |
| ALLOW_UNAUTHENTICATED| false | 是否允許未登入使用 |
## 檔案管理
| 變數名稱 | 預設值 | 說明 |
| ------------------------- | ------ | ------------------------------------ |
| AUTO_DELETE_EVERY_N_HOURS | 24 | 自動刪除超過 N 小時的檔案0 = 停用) |
## 進階設定
| 變數名稱 | 預設值 | 說明 |
| ------------------ | ------ | -------------------------------------- |
| WEBROOT | | 子路徑部署,例如 `/convertx` |
| FFMPEG_ARGS | | FFmpeg 輸入參數,例如 `-hwaccel vaapi` |
| FFMPEG_OUTPUT_ARGS | | FFmpeg 輸出參數,例如 `-preset veryfast` |
| HIDE_HISTORY | false | 隱藏歷史紀錄頁面 |
| MAX_CONVERT_PROCESS| 0 | 最大同時轉換數0 = 無限制) |
## 本地化設定
| 變數名稱 | 預設值 | 說明 |
| -------- | ------ | ----------------------------- |
| LANGUAGE | en | 日期格式語言BCP 47 格式) |
| TZ | UTC | 時區設定,例如 `Asia/Taipei` |
## 範例配置
### 開發環境
```yaml
environment:
- HTTP_ALLOWED=true
- ACCOUNT_REGISTRATION=true
```
### 生產環境
```yaml
environment:
- JWT_SECRET=your-very-long-and-random-secret-key
- ACCOUNT_REGISTRATION=false
- HTTP_ALLOWED=false
- TZ=Asia/Taipei
- AUTO_DELETE_EVERY_N_HOURS=48
```
### 公開服務(允許匿名使用)
```yaml
environment:
- ALLOW_UNAUTHENTICATED=true
- HIDE_HISTORY=true
- AUTO_DELETE_EVERY_N_HOURS=1
```

49
docs/converters.md Normal file
View file

@ -0,0 +1,49 @@
# 支援的轉換器
ConvertX-CN 完整版已內建以下所有轉換器,開箱即用。
## 轉換器列表
| 轉換器 | 用途 | 輸入格式數 | 輸出格式數 |
| ------ | ---- | ---------- | ---------- |
| [Inkscape](https://inkscape.org/) | 向量圖形 | 7 | 17 |
| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 |
| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 |
| [Vips](https://github.com/libvips/libvips) | 圖片 | 45 | 23 |
| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 |
| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 |
| [Calibre](https://calibre-ebook.com/) | 電子書 | 26 | 19 |
| [LibreOffice](https://www.libreoffice.org/) | 文件 | 41 | 22 |
| [Dasel](https://github.com/TomWright/dasel) | 資料檔案 | 5 | 4 |
| [Pandoc](https://pandoc.org/) | 文件 | 43 | 65 |
| [msgconvert](https://github.com/mvz/email-outlook-message-perl) | Outlook | 1 | 1 |
| VCF to CSV | 聯絡人 | 1 | 1 |
| [dvisvgm](https://dvisvgm.de/) | 向量圖形 | 4 | 2 |
| [ImageMagick](https://imagemagick.org/) | 圖片 | 245 | 183 |
| [GraphicsMagick](http://www.graphicsmagick.org/) | 圖片 | 167 | 130 |
| [Assimp](https://github.com/assimp/assimp) | 3D 模型 | 77 | 23 |
| [FFmpeg](https://ffmpeg.org/) | 影音 | ~472 | ~199 |
| [Potrace](https://potrace.sourceforge.net/) | 點陣轉向量 | 4 | 11 |
| [VTracer](https://github.com/visioncortex/vtracer) | 點陣轉向量 | 8 | 1 |
| [Markitdown](https://github.com/microsoft/markitdown) | 文件 | 6 | 1 |
> 註FFmpeg 的格式數量包含部分重複格式。
## 內建依賴
ConvertX-CN 完整版已預載:
| 類別 | 內建內容 |
| ---- | -------- |
| **文件轉換** | LibreOffice (headless)、Pandoc |
| **LaTeX** | TexLive Full完整版支援所有 LaTeX 需求) |
| **OCR 識別** | Tesseract OCR + 繁體中文、簡體中文、日文、韓文、英文、德文語言包 |
| **CJK 字型** | Noto CJK中日韓、Noto Emoji、微軟核心字型、標楷體 |
| **影音轉換** | FFmpeg、ImageMagick、GraphicsMagick |
| **向量圖形** | Inkscape、Potrace、VTracer、resvg |
| **電子書** | Calibre |
| **其他** | Ghostscript、MuPDF、Poppler、libheif、libjxl 等 |
## 新增轉換器
如需新增轉換器支援,請至 [GitHub Issues](https://github.com/pi-docket/ConvertX-CN/issues) 提出需求或直接發送 Pull Request。

68
docs/getting-started.md Normal file
View file

@ -0,0 +1,68 @@
# 快速開始
## 系統需求
- Docker 20.10+
- Docker Compose v2+(推薦)
- 至少 4GB RAM建議 8GB
- 10GB 磁碟空間(用於 Docker image
## 安裝方式
### 方法一Docker Run
```bash
docker run -d \
--name convertx-cn \
-p 3000:3000 \
-v ./data:/app/data \
-e TZ=Asia/Taipei \
convertx/convertx-cn:latest
```
### 方法二Docker Compose推薦
建立 `docker-compose.yml`
```yaml
services:
convertx:
image: convertx/convertx-cn:latest
container_name: convertx-cn
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./data:/app/data
environment:
- TZ=Asia/Taipei
- JWT_SECRET=請更換為一個長且隨機的字串
- ACCOUNT_REGISTRATION=false
- HTTP_ALLOWED=false
- AUTO_DELETE_EVERY_N_HOURS=24
```
啟動服務:
```bash
docker compose up -d
```
## 首次設定
1. 開啟瀏覽器訪問 `http://localhost:3000`
2. 建立第一個帳號(此帳號即為管理員)
3. 登入後即可開始使用
## 更新版本
```bash
docker compose pull
docker compose up -d
```
## 下一步
- 查看 [環境變數設定](./configuration.md)
- 瞭解 [支援的轉換器](./converters.md)
- 閱讀 [多語言設定](./i18n.md)

89
docs/i18n.md Normal file
View file

@ -0,0 +1,89 @@
# 多語言支援i18n
ConvertX-CN 支援多種語言的使用者介面。
## 支援語言
| 語言代碼 | 語言名稱 |
| -------- | ---------------- |
| zh-TW | 繁體中文(預設) |
| zh-CN | 简体中文 |
| en | English |
| ja | 日本語 |
| ko | 한국어 |
## 語言切換
1. 在網站右上角的導航列可看到語言選擇器(圖示)
2. 點擊後選擇偏好語言
3. 語言偏好會自動保存到 Cookie 中
4. 下次訪問時會記住您的偏好
## 自動偵測
首次訪問時,系統會根據瀏覽器的語言設定自動選擇最適合的語言。
優先順序:
1. Cookie 中儲存的語言偏好
2. 瀏覽器的 `Accept-Language` 標頭
3. 預設語言(繁體中文)
## 新增語言
如要添加新語言支援,請按以下步驟:
### 1. 建立翻譯檔案
`src/locales/` 目錄新增語言檔案,例如 `fr.json`(法文):
```json
{
"common": {
"appName": "ConvertX-CN",
"poweredBy": "Propulsé par",
...
},
"nav": {
"history": "Historique",
...
}
}
```
### 2. 註冊語言
`src/i18n/index.ts` 中:
```typescript
// 導入新語言檔案
import fr from "../locales/fr.json";
// 在 supportedLocales 陣列中添加
export const supportedLocales: LocaleConfig[] = [
// ... 現有語言
{ code: "fr", name: "French", nativeName: "Français" },
];
// 在 translations 物件中註冊
const translations: Record<SupportedLocale, TranslationData> = {
// ... 現有翻譯
fr: fr as TranslationData,
};
```
### 3. 更新類型定義
`SupportedLocale` 類型中添加新語言代碼:
```typescript
export type SupportedLocale = "zh-TW" | "zh-CN" | "en" | "ja" | "ko" | "fr";
```
## 貢獻翻譯
歡迎提交 Pull Request 來新增或改進翻譯!請確保:
- 翻譯完整(包含所有 key
- 語法正確
- 遵循現有的翻譯風格

View file

@ -3,7 +3,7 @@ import { type SupportedLocale, defaultLocale, getTranslations } from "../i18n";
export const BaseHtml = ({
children,
title = "ConvertX",
title = "ConvertX-CN",
webroot = "",
locale = defaultLocale,
}: {
@ -33,15 +33,15 @@ export const BaseHtml = ({
{children}
<footer class="w-full">
<div class="p-4 text-center text-sm text-neutral-500">
<span>{locale === "en" ? "Powered by " : `${getTranslations(locale).common.poweredBy} `}</span>
<span>Powered by </span>
<a
href="https://github.com/C4illin/ConvertX"
href="https://github.com/pi-docket/ConvertX-CN"
class={`
text-neutral-400
hover:text-accent-500
`}
>
ConvertX{" "}
ConvertX-CN
</a>
<span safe> v{version || ""}</span>
</div>

View file

@ -106,7 +106,7 @@ export const Header = ({
<ul>
<li>
<strong>
<a href={`${webroot}/`}>ConvertX</a>
<a href={`${webroot}/`}>ConvertX-CN</a>
</strong>
</li>
</ul>

View file

@ -13,14 +13,16 @@ export interface LocaleConfig {
}
export const supportedLocales: LocaleConfig[] = [
{ code: "en", name: "English", nativeName: "English" },
{ code: "zh-TW", name: "Chinese (Traditional)", nativeName: "繁體中文" },
{ code: "zh-CN", name: "Chinese (Simplified)", nativeName: "简体中文" },
{ code: "en", name: "English", nativeName: "English" },
{ code: "ja", name: "Japanese", nativeName: "日本語" },
{ code: "ko", name: "Korean", nativeName: "한국어" },
];
export const defaultLocale: SupportedLocale = "en";
// Default to zh-TW, fallback to en
export const defaultLocale: SupportedLocale = "zh-TW";
export const fallbackLocale: SupportedLocale = "en";
// Type definitions for translation keys
export type TranslationData = typeof en;

View file

@ -1,8 +1,7 @@
import { Elysia, t } from "elysia";
import { Elysia } from "elysia";
import {
type SupportedLocale,
createTranslator,
defaultLocale,
detectLocale,
getLocale,
} from "../i18n";
@ -12,29 +11,31 @@ import {
* Adds locale detection and translator to the context
*/
export const localeService = new Elysia({ name: "locale/service" })
.model({
localeSession: t.Cookie({
locale: t.Optional(t.String()),
}),
})
.derive({ as: "global" }, ({ cookie, request }) => {
// Get locale from cookie or detect from Accept-Language header
const cookieLocale = cookie.locale?.value;
.derive({ as: "global" }, ({ request }) => {
// Get locale from cookie (parsed from headers) or detect from Accept-Language header
const cookieHeader = request.headers.get("cookie") ?? "";
const acceptLanguage = request.headers.get("accept-language") ?? undefined;
// Parse locale from cookie string
let cookieLocale: string | undefined;
const localeMatch = cookieHeader.match(/locale=([^;]+)/);
if (localeMatch) {
cookieLocale = localeMatch[1];
}
let locale: SupportedLocale;
if (cookieLocale) {
if (cookieLocale && typeof cookieLocale === "string") {
locale = getLocale(cookieLocale);
} else {
locale = detectLocale(acceptLanguage);
}
const t = createTranslator(locale);
const translator = createTranslator(locale);
return {
locale,
t,
t: translator,
};
});

View file

@ -1,6 +1,6 @@
{
"common": {
"appName": "ConvertX",
"appName": "ConvertX-CN",
"poweredBy": "Powered by",
"version": "v{version}",
"loading": "Loading...",
@ -84,7 +84,7 @@
"noCookies": "No auth cookie, perhaps your browser is blocking cookies."
},
"setup": {
"welcome": "Welcome to ConvertX!",
"welcome": "Welcome to ConvertX-CN!",
"createYourAccount": "Create your account",
"reportIssues": "Report any issues on",
"github": "GitHub"

View file

@ -1,6 +1,6 @@
{
"common": {
"appName": "ConvertX",
"appName": "ConvertX-CN",
"poweredBy": "Powered by",
"version": "v{version}",
"loading": "読み込み中...",
@ -84,7 +84,7 @@
"noCookies": "認証Cookieが見つかりません。ブラウザがCookieをブロックしている可能性があります。"
},
"setup": {
"welcome": "ConvertXへようこそ!",
"welcome": "ConvertX-CNへようこそ!",
"createYourAccount": "アカウントを作成",
"reportIssues": "問題があれば報告",
"github": "GitHub"

View file

@ -1,6 +1,6 @@
{
"common": {
"appName": "ConvertX",
"appName": "ConvertX-CN",
"poweredBy": "Powered by",
"version": "v{version}",
"loading": "로딩 중...",
@ -84,7 +84,7 @@
"noCookies": "인증 쿠키를 찾을 수 없습니다. 브라우저에서 쿠키를 차단하고 있을 수 있습니다."
},
"setup": {
"welcome": "ConvertX에 오신 것을 환영합니다!",
"welcome": "ConvertX-CN에 오신 것을 환영합니다!",
"createYourAccount": "계정 생성",
"reportIssues": "문제가 있으시면 보고해 주세요",
"github": "GitHub"

View file

@ -1,6 +1,6 @@
{
"common": {
"appName": "ConvertX",
"appName": "ConvertX-CN",
"poweredBy": "由",
"version": "v{version}",
"loading": "加载中...",
@ -84,7 +84,7 @@
"noCookies": "无法获取验证 Cookie您的浏览器可能阻止了 Cookie。"
},
"setup": {
"welcome": "欢迎使用 ConvertX",
"welcome": "欢迎使用 ConvertX-CN",
"createYourAccount": "创建您的账户",
"reportIssues": "在以下网址报告问题",
"github": "GitHub"

View file

@ -1,6 +1,6 @@
{
"common": {
"appName": "ConvertX",
"appName": "ConvertX-CN",
"poweredBy": "由",
"version": "v{version}",
"loading": "載入中...",
@ -84,7 +84,7 @@
"noCookies": "無法取得驗證 Cookie您的瀏覽器可能阻擋了 Cookie。"
},
"setup": {
"welcome": "歡迎使用 ConvertX",
"welcome": "歡迎使用 ConvertX-CN",
"createYourAccount": "建立您的帳戶",
"reportIssues": "在以下網址回報問題",
"github": "GitHub"

View file

@ -33,7 +33,7 @@ export const history = new Elysia().use(userService).use(localeService).get(
userJobs = userJobs.filter((job) => job.num_files > 0);
return (
<BaseHtml webroot={WEBROOT} title="ConvertX | Results" locale={locale}>
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Results" locale={locale}>
<>
<Header
webroot={WEBROOT}

View file

@ -9,7 +9,7 @@ export const listConverters = new Elysia().use(userService).get(
"/converters",
async () => {
return (
<BaseHtml webroot={WEBROOT} title="ConvertX | Converters">
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Converters">
<>
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
<main

View file

@ -163,7 +163,7 @@ export const results = new Elysia()
.all(params.jobId);
return (
<BaseHtml webroot={WEBROOT} title="ConvertX | Result" locale={locale}>
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Result" locale={locale}>
<>
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn locale={locale} t={t} />
<main

View file

@ -73,7 +73,7 @@ export const user = new Elysia()
}
return (
<BaseHtml title="ConvertX | Setup" webroot={WEBROOT} locale={locale}>
<BaseHtml title="ConvertX-CN | Setup" webroot={WEBROOT} locale={locale}>
<main
class={`
mx-auto w-full max-w-4xl flex-1 px-2
@ -117,7 +117,7 @@ export const user = new Elysia()
text-accent-500 underline
hover:text-accent-400
`}
href="https://github.com/C4illin/ConvertX"
href="https://github.com/pi-docket/ConvertX-CN"
>
{t("setup", "github")}
</a>
@ -134,7 +134,7 @@ export const user = new Elysia()
}
return (
<BaseHtml webroot={WEBROOT} title="ConvertX | Register" locale={locale}>
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Register" locale={locale}>
<>
<Header
webroot={WEBROOT}
@ -258,7 +258,7 @@ export const user = new Elysia()
}
return (
<BaseHtml webroot={WEBROOT} title="ConvertX | Login" locale={locale}>
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Login" locale={locale}>
<>
<Header
webroot={WEBROOT}
@ -394,7 +394,7 @@ export const user = new Elysia()
}
return (
<BaseHtml webroot={WEBROOT} title="ConvertX | Account" locale={locale}>
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Account" locale={locale}>
<>
<Header
webroot={WEBROOT}