在雲端 Mac 建立 iOS 偽本地化版面回歸門檻

在雲端 Mac 建立 iOS 偽本地化版面回歸門檻

英文按鈕在開發機上可能只有六個字母,換成德文後卻可能占滿整行;左側的返回按鈕到了由右至左的介面中,也可能仍指向錯誤方向。若等到所有翻譯完成後才發現這些問題,修正時將同時影響版面、測試與發布時程。更穩妥的做法,是在雲端 Mac 的每次合併檢查中主動擴展字串、翻轉版面方向,並直接將不可見或無法操作的控制項判定為失敗。

先定義門檻要攔截的問題

偽本地化不是把介面替換成一組難以閱讀的字元,而是透過可控的轉換放大版面風險。建議先建立兩種模式:

門檻主要檢查四類問題:文字控制項的可見區域明顯不足;主要按鈕不存在、無法點擊或超出視窗;介面出現未經本地化入口處理的硬編碼文案;導覽、水平列表及方向圖示沒有隨 RTL 調整。

偽本地化適合揭露結構問題,並不負責判斷譯文是否正確。實際語言的驗收仍不可或缺,但不應承擔找出固定寬度這類基礎故障的成本。

建立可控的字串轉換層

不要在測試中逐一走訪視圖並修改標籤。這樣會繞過應用程式實際取得字串的路徑,也無法發現硬編碼。更可靠的方式,是讓專案既有的本地化入口支援一個只在偵錯建置中生效的轉換器。

enum PseudoMode: String {
    case expanded
    case rtl
}

#if DEBUG
func pseudoTransform(_ value: String, mode: PseudoMode?) -> String {
    guard let mode else { return value }

    switch mode {
    case .expanded:
        let padding = String(repeating: "·", count: max(2, value.count * 2 / 5))
        return "[\(value)\(padding)]"
    case .rtl:
        return "⁧\(value)⁩"
    }
}
#else
func pseudoTransform(_ value: String, mode: PseudoMode?) -> String {
    value
}
#endif

應用程式啟動時讀取 PSEUDO_LOCALE,且只接受明確的列舉值。所有面向使用者的字串都應經過統一入口,例如 L10n.text("checkout.confirm"),再交由轉換器處理。如果某個標題沒有顯示邊界標記,就能判定它繞過了本地化層。

RTL 模式還必須在應用程式根視圖設定語意方向。UIKit 可對測試視窗設定 semanticContentAttribute = .forceRightToLeft;SwiftUI 則可在根視圖注入 layoutDirection。不要反轉圖片內容,也不要強制顛倒數字、路徑與程式碼片段的順序。

使用 UI 測試檢查可見性與截斷

測試不需要逐像素比較。可先涵蓋登入後首頁、列表、詳細資料、表單及錯誤狀態等關鍵路徑,並為主要控制項設定穩定的 accessibility identifier。測試程序透過環境變數以對應模式啟動:

func launchApp(mode: String) -> XCUIApplication {
    let app = XCUIApplication()
    app.launchEnvironment["PSEUDO_LOCALE"] = mode
    app.launchArguments += ["-UI_TESTING", "1"]
    app.launch()
    return app
}

func testExpandedCheckoutLayout() {
    let app = launchApp(mode: "expanded")
    let confirm = app.buttons["checkout.confirm"]

    XCTAssertTrue(confirm.waitForExistence(timeout: 8))
    XCTAssertTrue(confirm.isHittable)
    XCTAssertFalse(confirm.frame.isEmpty)
    XCTAssertTrue(app.windows.firstMatch.frame.contains(confirm.frame))
}

isHittable 比單純檢查 exists 更有價值:按鈕可能存在於輔助使用樹狀結構中,卻被彈出層遮住或位於螢幕之外。對於標籤類控制項,可透過 identifier 取得 frame,並驗證它沒有超出所屬容器。不要使用 OCR 猜測文字是否遭到截斷;應優先讓關鍵元件在偵錯建置中公開 intrinsicContentSize 與實際 frame 的差值。

將檢查範圍整理成清單

情境expanded 檢查RTL 檢查失敗證據
導覽列標題與操作不重疊返回方向與順序正確介面階層、控制項 frame
表單標籤可換行,錯誤文案完整標籤與輸入軸一致欄位 identifier、目前值
列表長標題不會擠掉主要操作附件與箭頭位置正確失敗列索引、螢幕截圖
彈出層按鈕完整且可點擊操作順序符合語意彈出層 frame、元素樹

在雲端 Mac 上組織執行環境

為每個工作建立獨立的模擬器裝置集,避免並行工作共用啟動狀態。裝置集目錄應放在工作的暫存目錄中,結束時無論成功或失敗都要執行清理。測試前固定 Xcode 路徑、模擬器機型與系統版本,並將這些值寫入報告。

set -euo pipefail

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
export PSEUDO_LOCALE="${PSEUDO_LOCALE:-expanded}"
RESULT_DIR="$PWD/TestResults/$PSEUDO_LOCALE"
mkdir -p "$RESULT_DIR"

xcodebuild test \
  -workspace App.xcworkspace \
  -scheme AppUITests \
  -destination "platform=iOS Simulator,name=iPhone 16,OS=latest" \
  -resultBundlePath "$RESULT_DIR/LayoutTests.xcresult" \
  PSEUDO_LOCALE="$PSEUDO_LOCALE"

expanded 與 rtl 應作為兩個獨立工作執行,而不是在同一個模擬器工作階段中切換。如此一來,失敗原因可明確歸屬,重新執行時也不會沿用上一個方向模式。若雲端 Mac 同時執行多個工作,每個工作還必須使用獨立的 DerivedData 路徑,以免測試產物互相覆寫。

處理常見誤報與實際故障

最常見的誤報來自動畫尚未結束、系統彈出層未關閉,以及測試資料長度不穩定。解決方式不是統一增加等待時間,而是等待明確狀態:載入指示器消失、目標元素出現、視窗數量穩定。測試資料也必須固定,尤其是使用者名稱、日期與伺服器錯誤訊息。

實際故障通常集中在固定高度、單行標籤、手動計算寬度,以及僅以視覺位置表達順序的元件。修正時應優先移除限制條件,而不是針對特定語言增加例外。允許按鈕標題換行後,還要重新檢查點擊區域是否涵蓋完整文字。

發現失敗時,至少應封存以下內容:

  1. 目前的偽語言模式、Xcode 與模擬器版本。
  2. 失敗控制項的 identifier、frame、label,以及是否可點擊。
  3. 目前頁面的輔助使用階層。
  4. 對應的 xcresult 與失敗時的螢幕截圖。
  5. 重現該頁面所需的固定測試資料。

設定合併門檻並防止洩漏

門檻應阻擋會影響工作完成的問題:主要操作不可見或無法點擊、文字截斷後失去原意、RTL 順序錯誤,以及互動控制項缺少可存取名稱。輕微的間距變化可記錄為待複核項目,不必讓所有像素差異都阻止合併。

最後再加入一項 Release 驗收:掃描建置設定與應用程式資源,確認沒有測試專用的偽語言檔案,並驗證正式建置會忽略 PSEUDO_LOCALE。同時在正常模式下執行一組冒煙測試,避免轉換層本身改變字串。

這套流程的價值不在於產生一個看起來奇怪的介面,而是把多語言版面從發布前的人工抽查,轉化為每次程式碼變更都能重複執行的工程約束。先涵蓋主要路徑,再逐步加入錯誤狀態與低頻彈出層,才能讓門檻保持穩定且易於維護。

常見問題

偽本地化測試可以取代真實語言驗收嗎?

不可以。它能快速找出固定寬度、未本地化字串與雙向版面錯誤,但最後仍須使用至少一種長文字語言及一種 RTL 語言驗收真實文案。

如何避免偽本地化程式碼進入正式版本?

把轉換器限制在 DEBUG 編譯條件內,並讓 Release 建置在偵測到 PSEUDO_LOCALE 環境變數或測試專用資源時直接失敗。

哪些版面問題應該阻擋合併?

核心操作不可見、按鈕無法點擊、文字截斷至失去意義、RTL 順序錯誤及輔助使用標籤缺失都應阻擋合併。

獨享實體節點

將下一次建置放到雲端 Mac mini

選擇配置、計費週期與節點區域,使用獨享實體機執行開發、建置與自動化工作。

立即租用 Mac mini