SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

Claude Code 繁體中文終端機:亂碼、對齊與回覆語言

用 SSH 連 Ubuntu VPS 跑 Claude Code,中文亂碼、tmux 欄位錯位、模型回簡體:從 locale charmap 判斷問題在哪一邊,產生 zh_TW.UTF-8,再用 CLAUDE.md 與 output style 固定回覆語言。

Claude Code 在繁體中文終端機的三個問題

在繁體中文終端機裡用 SSH 連上 Ubuntu VPS 跑 Claude Code,會壞掉的地方其實只有三個。第一個是伺服器上沒有產生 zh_TW.UTF-8 語系,中文就變成亂碼。第二個是中日韓漢字佔兩格,把 tmux、less 和 Claude Code 自己的框線推歪,這是你本機終端機和字型的問題,不是伺服器的問題。第三個是你用繁體提問,模型回你簡體,這要寫進 CLAUDE.md 和 output style,不是每次對話重講一遍。

這三件事必須分開處理,因為修錯那一邊,畫面不會有任何改善。本文只談終端機這一層:顯示、對齊、輸出語言。

第一步:先分清楚問題在伺服器還是在你的終端機

登入伺服器之後,先跑這三行。它們不改任何設定。

locale
locale charmap
echo 繁體中文 | wc -m

locale 印出這個 shell 目前的語系設定。locale charmap 印出目前 LC_CTYPE 對應的字元集,正常是 UTF-8;如果印出 ANSI_X3.4-1968,代表 LC_CTYPE 已經退回 C 語系,那是純 ASCII。

第三行是最快的判斷法。「繁體中文」是四個字,在 UTF-8 下每個漢字佔三個位元組,加上換行總共十三個位元組。wc -m 數的是字元:語系正確時它印出 5,語系退回 C 時它印出 13,因為它把每個位元組都當成一個字元。

印出 5 就代表伺服器這一端沒事,亂碼和錯位都發生在你本機的終端機,跳到後面關於雙寬字元的那一節。印出 13 就先修伺服器的語系。

中文變成亂碼,因為伺服器沒有 zh_TW.UTF-8 這個語系

亂碼不是 SSH 把位元組傳壞了。SSH 是位元組通道,它不改內容。壞掉的是伺服器上的程式怎麼解讀那些位元組。

程式啟動時呼叫 setlocale() 要求 zh_TW.UTF-8。系統上沒有編譯好的這個語系,呼叫就失敗,LC_CTYPE 退回 C。之後 less、wc、readline 這些靠 mbrtowc() 判斷字元邊界的程式,就把一個三位元組的漢字當成三個各自獨立、而且不可列印的位元組。less 會把它們一個一個當成不可列印的位元組標示出來,那不是壞掉的字,那是 less 在說「這些位元組我不認得」。不同的分頁器和終端機標示的方式不一樣,所以不要用畫面上的符號長相來判斷,要看 locale charmap 和 wc -m 的數字。

語系缺席的時候,locale 自己就會先抱怨:

locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=zh_TW.UTF-8
LANGUAGE=
LC_CTYPE="zh_TW.UTF-8"

注意最後兩行:LANG 確實是 zh_TW.UTF-8,值有設,語系卻不存在。同一件事在跑 apt 的時候會換一張臉出現:

perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
	LANGUAGE = (unset),
	LC_ALL = (unset),
	LANG = "zh_TW.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

這段警告不代表 apt 壞了,它跟上面那三行講的是同一個問題。

那麼 LANG 是誰設的?是你的本機送過去的。OpenSSH 的用戶端設定裡有 SendEnv LANG LC_*,伺服器的 /etc/ssh/sshd_config 裡有對應的 AcceptEnv LANG LC_*。macOS 的 Terminal.app 和 iTerm2 都有一個「啟動時設定語系環境變數」的選項,預設是開的,所以它把你本機的 LANG=zh_TW.UTF-8 一起送上去。伺服器收下這個值,卻沒有這個語系可以用,於是每一個新 shell 都在同一個地方失敗。

看一下伺服器手上到底有什麼:

locale -a

剛開好的 Ubuntu 24.04 映像通常只列出 C、C.utf8、POSIX,有些映像會多一個 en_US.utf8。沒有任何 zh_TW 開頭的項目,這就是原因。

產生 zh_TW.UTF-8,並讓它每次登入都生效

產生語系的工具來自 locales 套件,Debian 和 Ubuntu 都一樣,所以這條路比去猜中文語言包的套件名稱可靠。語言包只多給你翻譯過的介面訊息,它不是產生語系的必要條件。

command -v locale-gen || sudo apt install -y locales
sudo sed -i 's/^# *zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/' /etc/locale.gen
sudo locale-gen

locale-gen 不帶參數時會讀 /etc/locale.gen,把裡面沒有被註解掉的每一行都產生一次。輸出長這樣:

Generating locales (this might take a while)...
  zh_TW.UTF-8... done
Generation complete.

你也可以直接寫 sudo locale-gen zh_TW.UTF-8,一樣會產生。但把那一行留在 /etc/locale.gen 裡比較好,因為 locales 套件更新時會重跑一次,而它只認得檔案裡的內容。

確認結果,然後設成系統預設:

locale -a | grep zh_TW
sudo update-locale LANG=zh_TW.UTF-8
cat /etc/default/locale

第一行應該印出 zh_TW.utf8。locale -a 印的是正規化後的名字,所以是小寫、沒有連字號,這跟你在 LANG 裡寫的 zh_TW.UTF-8 是同一個語系。cat 應該印出 LANG=zh_TW.UTF-8。

update-locale 寫的是 /etc/default/locale。Ubuntu 的 /etc/pam.d/sshd 裡有一行 pam_env 指向這個檔案,所以下一次登入時這個值就會進到環境裡。這也是為什麼改完不用重開機,登出再登入就好。

開一條新連線驗證:

ssh my-vps locale charmap
echo 繁體中文 | wc -m

第一個指令要印出 UTF-8,第二個要印出 5。兩個都對了,伺服器這一端就結束了。

如果你的本機還在送語系變數,送過來的值會蓋掉 /etc/default/locale 的值。現在兩邊都是 zh_TW.UTF-8,所以不衝突。想把它固定下來,在 ~/.ssh/config 裡明確指定,不要讓它看終端機的心情:

Host my-vps
    HostName 203.0.113.10
    User ubuntu
    SetEnv LANG=zh_TW.UTF-8

要臨時在單一指令上套用語系,直接前置環境變數,這在你還沒決定要不要改系統預設的時候很好用:

LC_ALL=zh_TW.UTF-8 less notes.md

tmux、less 和分頁器的欄位為什麼會歪掉

這一段的問題全都在你本機。伺服器送出去的是位元組,格子是你的終端機畫的。

一個漢字在等寬字型裡佔兩格。終端機、tmux、還有 Claude Code 的介面各自用自己的寬度表去算「這一行佔幾格」。三者算出同一個數字,畫面就整齊。其中一個算不一樣,框線就開始漂,而且越往下漂得越多。

漂移最常見的來源是 East Asian Ambiguous。Unicode 把一批字元標成「寬度看情況」,例如畫框線用的 ─ 和 │、項目符號 ●,還有全形的引號。標準沒有規定它們是一格還是兩格,所以每個終端機自己決定。iTerm2 的開關在 Profiles > Text,叫 Treat ambiguous-width characters as double width。開或關都可以,重點是 tmux 和終端機不要各算一套。

tmux 需要知道自己在 UTF-8 環境裡。它從 LC_ALL、LC_CTYPE、LANG 這三個變數判斷,判斷失敗就要用 tmux -u 強制。這裡有一個順序問題:tmux server 是常駐的,它在 C 語系下被啟動,之後你把語系修好了,那個 server 還是錯的。修好語系之後要 tmux kill-server 再重開,畫面才會正常。如果你是把 Claude Code 放進 tmux 裡長時間執行,在 VPS 上用 tmux 讓 Claude Code 保持執行不中斷 的做法要先確認語系,再開第一個 session。

寬度本身也要對得上:

tput cols
stty size

兩個數字要一致,而且要等於你視窗實際的字元寬度。不一致代表視窗大小變更的訊號沒有傳到,程式算錯換行位置,中文就會在半個字的地方斷開。把視窗拉一下通常會重新送出訊號;在 tmux 裡改用 tmux attach -d 重新接上,會依你現在的視窗重算一次。

另一個容易和亂碼搞混的症狀是 TERM 對不上。echo $TERM 印出 xterm-256color,但伺服器上沒有那個 terminfo 項目,程式就會抱怨然後退回很笨的模式,畫面一樣難看。那是不同的原因,SSH 出現 unknown terminal type 錯誤 裡有完整的處理方式。

分頁器這邊,語系修好之後 less 就會照 UTF-8 處理中文。如果它還在把中文拆成一個個位元組顯示,回頭看 locale charmap,問題還在語系。把 LESSCHARSET=utf-8 塞進 ~/.bashrc 只是繞過症狀,別的程式還是會錯。

還有一個只有中文使用者會遇到的設定:

git config --global core.quotepath false

沒有這一行,git status 會把中文檔名印成 "\347\271\201\351\253\224.md"。這不是亂碼,是 git 自己把非 ASCII 位元組轉成八進位逃脫序列。Claude Code 會讀 git status 的輸出,所以這一行同時讓它看到真正的檔名,而不是一串反斜線。

Claude Code 用繁體提問卻回簡體,怎麼一次改掉

模型看到的不只有你打的字,還有系統提示和專案裡的檔案。中文訓練資料裡簡體佔多數,所以你只給它幾十個繁體字,它有時候會滑回簡體,或是字形是繁體但用詞是簡體圈的習慣:把「檔案」寫成「文件」,「程式」寫成「程序」。

要它穩定,就把要求放進每一次請求都會送出的地方,而不是在對話裡講一次。有兩個地方可以放,用途不同。

第一個是 CLAUDE.md。放專案根目錄的 ./CLAUDE.md,或是所有專案共用的 ~/.claude/CLAUDE.md。這裡寫的是「關於這個專案你該知道的事」,語言偏好算在裡面。

## 語言
一律使用繁體中文(台灣用語)回覆,程式碼註解也用繁體中文。
不要輸出簡體字。用「檔案」、「程式」、「伺服器」、「預設值」、「記憶體」,不要用「文件」、「程序」、「默认值」。
指令、旗標、路徑、環境變數、套件名稱、識別字保持原文,不要翻譯,也不要在中英文之間硬加空格以外的修飾。

第二個是 output style。它管的是「每一次回覆的角色、語氣和形式」,整個 session 都套用。把檔案存成 ~/.claude/output-styles/zh-hant.md,檔名之外的欄位寫在 YAML 前置資料裡:

---
name: 繁體中文
description: 一律以繁體中文回覆,指令與識別字保留原文
keep-coding-instructions: true
---

一律使用繁體中文(台灣用語)回覆。不要輸出簡體字。

## 用詞
檔案、程式、伺服器、預設值、記憶體、演算法、效能。不要使用文件、程序、默认值這一類用語。

## 保持原文
指令、旗標、路徑、環境變數、程式碼、識別字、套件名稱都保持原文。解釋它們的意思,不要翻譯它們本身。

keep-coding-instructions: true 這一行不能省。自訂 output style 預設會拿掉 Claude Code 內建的軟體工程指令,也就是它怎麼控制修改範圍、怎麼寫註解、怎麼驗證結果那一整套。你只是想改回覆的語言,不是要換掉它的工作方式,所以要把那些指令留著。

檔案寫好之後切換:

claude

進去之後執行 /output-style。不帶參數時它會列出可選的樣式,並標出目前這一個;/output-style 繁體中文 直接切換。選擇會寫進專案的 .claude/settings.local.json,欄位叫 outputStyle。想讓它成為所有專案的預設,把 "outputStyle": "繁體中文" 寫進 ~/.claude/settings.json。這個值區分大小寫,拼錯就等於沒設定,而且不會有任何錯誤訊息。

有一個時機問題會讓人以為設定沒效:Claude Code 在啟動時讀 output style 檔案。你在執行中的 session 新增或修改了那個檔案,要重開 Claude Code 才會被讀到。切換樣式本身則是從你的下一則訊息開始生效。

這兩個機制都是指令,不是保證。官方文件自己就這樣寫:output style 是給模型的指示,它不保證某件事一定發生。長對話的後段偶爾還是會滑回簡體,看到就當場說一句「請用繁體中文」,它會修正,而且不用重開 session。真正要「每次都一定發生」的動作,例如每次編輯後跑格式化,那要用 hook,因為 hook 是 Claude Code 自己執行的,不靠模型記得。

至於 Claude 寫出來的繁體中文品質好不好、用詞夠不夠台灣,那是另一個題目,跟終端機無關。如果你還在評估要不要把它放到 VPS 上跑,先看 Claude Code 是什麼,以及它在終端機裡實際做哪些事。

一次檢查完的順序

照這個順序跑一遍,每一項都有明確的預期輸出。

  1. locale charmap 印出 UTF-8,不是 ANSI_X3.4-1968。
  2. echo 繁體中文 | wc -m 印出 5,不是 13。
  3. locale -a | grep zh_TW 印出 zh_TW.utf8。
  4. cat /etc/default/locale 裡有 LANG=zh_TW.UTF-8。
  5. 重新登入之後,上面四項還是一樣的結果。
  6. tput cols 和 stty size 的寬度一致,而且等於視窗的實際寬度。
  7. tmux 裡的框線不會隨著畫面往下漂。修完語系記得 tmux kill-server 重開。
  8. git status 印出真正的中文檔名,不是 \347\271\201 這種逃脫序列。
  9. Claude Code 的第一句回覆就是繁體中文。

任何一項不過,就停在那一項。往後修不會讓前面的問題自己好起來。

FAQ 常見問題

為什麼 SSH 連上伺服器後中文變成亂碼?

因為你的終端機把 LANG=zh_TW.UTF-8 送到伺服器,而伺服器上沒有產生這個語系。程式呼叫 setlocale() 失敗,LC_CTYPE 退回 C,之後 less 和 wc 這些程式就把一個三位元組的漢字當成三個不可列印的位元組,畫面上就是一堆拼不回中文的符號。用 locale charmap 判斷:印出 ANSI_X3.4-1968 就是這個原因,印出 UTF-8 表示伺服器沒問題,要去看本機終端機的字型和寬度設定。

需要安裝中文語言包嗎,還是只要設 LANG 就夠了?

只設 LANG 不夠,因為 LANG 只是一個字串,語系檔案要另外產生出來。用 locales 套件提供的 locale-gen 產生 zh_TW.UTF-8,再用 update-locale LANG=zh_TW.UTF-8 寫進 /etc/default/locale,這條路在 Debian 和 Ubuntu 上都一樣。語言包是另一回事,它提供翻譯過的介面訊息,讓 ls 之類的程式用中文報錯;顯示和對齊不需要它。

tmux 裡的框線和欄位為什麼會歪掉?

因為漢字佔兩格,而終端機、tmux 和程式本身各自算出的寬度不一致。最常見的是 East Asian Ambiguous 這一類字元,像 ─、│、● 和全形引號,標準沒規定它們是一格還是兩格。把終端機的 ambiguous width 設定和 tmux 對齊,兩邊用同一套算法。另外,tmux server 在 C 語系下啟動之後就會一路錯下去,語系修好要 tmux kill-server 重開,或啟動時加 tmux -u。

Claude Code 一直回簡體中文,怎麼固定成繁體?

把要求寫進每次請求都會送出的地方。在 ~/.claude/CLAUDE.md 或專案的 CLAUDE.md 裡寫明一律使用繁體中文(台灣用語)、不要輸出簡體字、指令和識別字保持原文。再加一個 output style:檔案放 ~/.claude/output-styles/zh-hant.md,前置資料裡設 name、description 和 keep-coding-instructions: true,然後用 /output-style 切換。Claude Code 啟動時才讀樣式檔,所以新增檔案之後要重開一次。這是指令而不是保證,偶爾滑回簡體時當場說一句就會修正。