e37aade69f
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
11 KiB
下拉選項雙語顯示(English/中文)— 設計文件
日期: 2026-05-29 狀態: 已核准設計,待寫實作計畫 範圍: 前端(Angular)。不動資料庫、不動 schema、不動既有後端 API 回傳的列表名稱。
1. 目標
針對大多數下拉選單,選項同時顯示英文與中文,格式為 英文/中文(例:Worship/敬拜、Cash/現金)。
設計決定(已與使用者確認):
- 顯示時拼接、資料分開存 — 中英文在資料層維持分開(DB 兩欄 / 前端 value 不變),只在「顯示」時組成
英文/中文。送出/儲存的值完全不變。 - 全站套用 + 未來約定 — 現有所有業務下拉都改;並定成往後新增下拉的統一慣例。
- 回顯一致性 — 下拉改雙語後,前端自己組得出來的已選值回顯也一起改(目前僅奉獻錄入頁下方明細的 Type 欄)。後端帶
name_en過來的列表欄位(如支出列表)本次不動。
格式規則:
- 英文在前、中文在後,中間以
/分隔、無空格。 - 沒有中文時只顯示英文(如品牌字
Zelle、PayPal)。
2. 現況盤點
下拉選項來源分兩類:
第 1 類 — DB 查表(已有 name_en + name_zh,中文已種子,但前端只顯示英文)
後端實體與前端 DTO 皆已具備 name_en(非空)+ name_zh(可空),種子資料已填中文(如 Worship/敬拜、Tithe/什一奉獻)。前端目前一律綁 textField="name_en",因此中文存在卻未顯示。故此類為純前端顯示改動,不需動 DB 或 schema。
涉及下拉:
| 下拉 | 模板位置 | 資料來源 |
|---|---|---|
| 奉獻錄入「Type」 | app/features/giving/pages/offering-session-page/offering-session-page.component.html:39 |
categories ← GivingCategoryApiService.getAll |
| Givings 類型篩選 + 對話框「Type」 | app/features/giving/pages/givings-page/givings-page.component.html:9, 48 |
categories |
| 支出「Ministry」篩選 | app/features/expense/pages/expenses-page/expenses-page.component.html:18 |
ministries ← MinistryApiService.getAll |
| 支出對話框 Ministry / 大類 / 子項(3 個) | app/features/expense/components/expense-form-dialog/expense-form-dialog.component.html:19, 32, 45 |
ministries / groups / subs |
第 2 類 — 前端寫死的 enum 陣列(後端僅存 string,無 C# enum)
散落在各 *.component.ts,標籤目前為純英文(部分為純字串陣列、部分已是 {text,value} 物件陣列)。
| Enum | 型別/陣列位置 | 綁定形式 |
|---|---|---|
| PaymentMethod | giving.model.ts:1;陣列 offering-session-page.component.ts:36、givings-page.component.ts:44 |
純字串陣列(直接綁字串) |
| ExpenseStatus | expense.model.ts:2;陣列 expenses-page.component.ts:34 |
純字串陣列 |
| Member Status | member-form-dialog.component.ts:30、members-page.component.ts:39 |
純字串陣列 |
| Gender | member-form-dialog.component.ts:31-35 |
{text,value} 物件陣列 |
| Language | member-form-dialog.ts、users/create-user-dialog.ts、users/edit-user-dialog.ts、members/create-user-dialog.ts |
{text,value} 物件陣列(已含「中文」) |
| Roles(multiselect) | roleOptions: string[] = [...ALL_ROLES];ALL_ROLES 於 users/models/user.model.ts:48 |
純字串陣列(13 個 snake_case 代碼) |
Dashboard 範本殘留的示範下拉(
dashboard.html)與未使用的通用元件不在範圍內。
3. 做法:方案 A(共用工具 + 兩處收斂)
評估過三種做法:
- 方案 A(採用) — 共用
bilingual()工具;DB 查表類在 service 層算出label,enum 類集中到option-lists.ts。模板改動最小、與既有textField寫法一致、可定為全站慣例。 - 方案 B(不採用)— 每個下拉用 Kendo item/value
<ng-template>+ pipe。不動 DTO,但每個下拉需兩段樣板,8 個下拉冗長易漏,未來新下拉也須每次手寫。 - 方案 C(不採用)— 後端 C# 直接回傳合併字串。違反「前端顯示時拼接、資料分開存」,且幫不到第 2 類前端 enum。
4. 詳細設計
4.1 共用工具(新檔)
APP/src/app/shared/i18n/bilingual.ts
/** 顯示用雙語標籤:英文在前、中文在後,無中文則只回英文。 */
export const bilingual = (en: string, zh?: string | null): string =>
zh ? `${en}/${zh}` : en;
APP/src/app/shared/i18n/option-lists.ts — 集中所有寫死 enum 的雙語選項,型別為 ReadonlyArray<{ value: string; label: string }>。供全站及未來新下拉引用。
4.2 第 1 類:DB 查表下拉
- DTO 加唯讀
label(可空,顯示用):GivingCategoryDto、MinistryDto、ExpenseCategoryGroupDto、ExpenseSubCategoryDto各加label?: string;。 - Service 載入時計算
label:在GivingCategoryApiService、MinistryApiService、ExpenseCategoryApiService(含 groups 與 subs)的回傳map中,對每筆資料補label: bilingual(x.name_en, x.name_zh)。- 集中在 service 層的理由:所有現有消費端自動雙語;未來新下拉只要綁同一 service 即免費雙語 → 即「未來約定」。
- 模板:對應下拉的
textField="name_en"全部改為textField="label"。 - defaultItem 提示字改雙語:例如
{ id: null, name_en: 'All types' }→{ id: null, label: 'All types/全部類型' }{ id: null, name_en: 'All Ministries' }→{ id: null, label: 'All Ministries/全部事工' }- 「-- Select ministry --」等 → 改為對應雙語提示。
(defaultItem 物件需改為帶
label欄以對應新的textField。)
- 奉獻明細回顯:
offering-session-page.component.ts的addLine()中categoryName: cat?.name_en ?? ''改為categoryName: cat?.label ?? '',使下方明細 Type 欄與下拉一致。
4.3 第 2 類:寫死 enum 下拉(集中到 option-lists.ts)
預設譯名(使用者已核可,可日後再微調):
export const PAYMENT_METHOD_OPTIONS = [
{ value: 'Cash', label: 'Cash/現金' },
{ value: 'Check', label: 'Check/支票' },
{ value: 'Zelle', label: 'Zelle' },
{ value: 'PayPal', label: 'PayPal' },
{ value: 'Other', label: 'Other/其他' },
] as const;
export const EXPENSE_STATUS_OPTIONS = [
{ value: 'Draft', label: 'Draft/草稿' },
{ value: 'PendingApproval', label: 'PendingApproval/待審核' },
{ value: 'Approved', label: 'Approved/已核准' },
{ value: 'Paid', label: 'Paid/已付款' },
{ value: 'Rejected', label: 'Rejected/已拒絕' },
] as const;
export const MEMBER_STATUS_OPTIONS = [
{ value: 'Member', label: 'Member/會友' },
{ value: 'Visitor', label: 'Visitor/訪客' },
{ value: 'Inactive', label: 'Inactive/未活躍' },
{ value: 'Former', label: 'Former/已離開' },
] as const;
export const GENDER_OPTIONS = [
{ value: 'M', label: 'Male/男' },
{ value: 'F', label: 'Female/女' },
{ value: 'Other', label: 'Other/其他' },
] as const;
export const LANGUAGE_OPTIONS = [
{ value: 'en', label: 'English/英文' },
{ value: 'zh-TW', label: '中文/Chinese' },
] as const;
export const ROLE_OPTIONS = [
{ value: 'super_admin', label: 'Super Admin/系統管理員' },
{ value: 'pastor', label: 'Pastor/牧師' },
{ value: 'board_member', label: 'Board Member/理事' },
{ value: 'coworker_chair', label: 'Coworker Chair/同工會主席' },
{ value: 'ministry_leader', label: 'Ministry Leader/事工領袖' },
{ value: 'district_leader', label: 'District Leader/區長' },
{ value: 'cell_leader', label: 'Cell Leader/小組長' },
{ value: 'coworker', label: 'Coworker/同工' },
{ value: 'finance', label: 'Finance/財務同工' },
{ value: 'secretary', label: 'Secretary/行政秘書' },
{ value: 'worship_leader', label: 'Worship Leader/敬拜領袖' },
{ value: 'member', label: 'Member/一般教友' },
{ value: 'visitor', label: 'Visitor/訪客' },
] as const;
綁定調整:
- 原本綁純字串陣列者(PaymentMethod、ExpenseStatus、Member Status)改用上述物件陣列,模板補
textField="label" valueField="value" [valuePrimitive]="true"。儲存/送出的值維持原本字串,不變。 - Gender、Language 已是物件陣列,改引用集中常數;為全站一致,欄名統一用
label(取代原本的text),模板對應改textField="label"。 - Roles multiselect 改綁
ROLE_OPTIONS,加textField="label" valueField="value" [valuePrimitive]="true",確保formControlName="roles"仍取得代碼陣列。 - 含篩選用空值的(如 members-page 的
''= 全部)保留一個{ value: '', label: 'All Status/全部狀態' }或沿用defaultItem,行為不變。
4.4 約定文件
docs/PLANNING.md 新增一節「下拉雙語顯示約定」:
- DB 查表下拉 → 用 service 計算的
label(bilingual(name_en, name_zh)),模板綁textField="label"。 - 寫死 enum 下拉 → 一律定義在
shared/i18n/option-lists.ts,{value,label},模板綁textField="label" valueField="value" [valuePrimitive]="true"。 - 格式
英文/中文、無空格、無中文則只英文。
5. 不在範圍(YAGNI / 本次不做)
- 不導入 Angular i18n / 翻譯框架(使用者要的是「同時顯示兩種語言」,非切換語系)。
- 不改 DB、不改 EF 實體、不改 migration、不改種子資料。
- 不改後端 API 回傳的列表名稱欄位(如支出列表的 ministry/category 名稱),故該等表格欄位本次維持英文。
- Dashboard 範本示範下拉與未使用的通用元件不處理。
6. 驗證
- 前端
build通過(型別正確)。 - 起 dev server,以 preview 截圖確認:
- 奉獻錄入頁:Type 顯示「Worship/敬拜」等、Method 顯示「Cash/現金」等;加入一筆後,下方明細 Type 欄同步雙語。
- 支出對話框:Ministry / 大類 / 子項雙語。
- 會友表單:Gender、Status、Language 雙語。
- 使用者對話框:Roles multiselect 雙語。
- 確認送出後實際儲存的值不變(仍為
Cash、Member、角色代碼、類別 id 等)。
7. 影響檔案清單(預估)
新增
APP/src/app/shared/i18n/bilingual.tsAPP/src/app/shared/i18n/option-lists.ts
修改(DTO / Service)
app/features/giving/models/giving.model.ts(GivingCategoryDto加label)app/features/expense/models/expense.model.ts(MinistryDto、ExpenseCategoryGroupDto、ExpenseSubCategoryDto加label)GivingCategoryApiService、MinistryApiService、ExpenseCategoryApiService(map 計算label)
修改(模板 / 元件)
offering-session-page.component.{html,ts}givings-page.component.{html,ts}expenses-page.component.{html,ts}expense-form-dialog.component.{html,ts}member-form-dialog.component.{html,ts}members-page.component.{html,ts}users/create-user-dialog.component.{html,ts}、users/edit-user-dialog.component.{html,ts}members/create-user-dialog.component.{html,ts}
文件
docs/PLANNING.md(新增約定一節)